前言
JS 中的垃圾回收算法有两个版本:
- 引用计数
- 标记清除
在 c 语言中,内存的申请、使用和释放(内存生命周期)每一步都是开发者控制;在自动 gc 的语言中,例如 js 中,开发者只感知内存的使用。
引用计数算法
简单说:如果对象被其它对象引用,那么就不能被垃圾回收。
原理:每个对象有个属性值,来标识它被引用的次数。为 0 的时候,可以垃圾回收。
例如:
1 | let a = { |
引用计数缺陷
引用计数有个问题,当“循环引用”的时候,无法回收内存。
例如:
1 | function f() { |
标记清除算法
原理:假定设置一个叫做根(root)的对象(在 Javascript 里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。
step1: 从根开始寻找
step2: DFS 找到被引用的对象。
step3: 清除其它对象,只保留找到的对象。
如何解决循环引用?
还是上面那个例子:
1 | function f() { |
f 是全局对象(例如:浏览器的 window、node 的 global)上的属性,也就是 root 的子节点。
执行函数时,a 和 b 成为 f 的子节点。此时,如果 gc 触发,那么 f、a 和 b 都不会被回收。
当函数执行完毕,退出 f 的函数作用域,a 和 b 只是互相引用,但不再是 f 的子节点。如果 gc 触发,a 和 b 节点不在树上,会被回收。
参考
- Memory Management
- 如何分析 Node.js 中的内存泄漏: 注意 keepalive 场景下,复用 socket 重复监听导致内存泄漏问题