面试是最好的照妖镜,能够从专业人士的眼光中看出自己哪些还有不足。这个系列就用来记录一下容易被忽视掉的基础。
参考自:Understanding JavaScript Memory Management using Garbage Collection
目标
垃圾回收机制不是一个新名词,主要就是由引擎来对我们分配出去的内存空间做统一的管理和回收。希望通过这篇文章的总结,可以了解以下几个问题:
- JS的垃圾回收算法是什么
- 垃圾回收的时机
- 能不能人为干预JS的回收机制
JS的垃圾回收机制
JS的垃圾回收机制主要是从内存中删除那些无法到达的对象。主要由下面两种算法实现:
- 引用计数回收
- 标记扫描算法
引用计数回收
实例
这是一个很简单的算法。主要就是看哪些对象没有被引用。如果一个对象没有被任何引用指向的话,那么它就要被回收。
1 | var obj1 = { |
创建如上图的代码例子。在这个例子中,obj1
有一个property1
对象,而property1
也有一个subproperty1
对象。因为obj1
有引用指向到对象,因此不会被回收掉。
1 | var obj2 = obj1 |
现在,obj2
同样指向了obj1
所指向的对象,但是随后obj1
就被更新成了"some random text"
的一个字符串,因此obj2
就成了唯一指向那个对象的引用。
1 | var obj_property1 = obj2.property1 |
现在obj_property1
指向了obj2.property1
,同样持有该对象的引用。就是说,当前的对象拥有两个引用:
obj2
指向对象本身obj_property1
指向对象的子对象property1
1 | obj2 = "some random text" |
把obj2
解除引用并更新为"some random text"
字符串。那么,它之前所指向的对象看起来就已经没有了引用,应该能被回收了吧?实际上并不是的,因为obj_preperty1
依然指向着这个对象的子对象obj2.preperty1
,因此它是不会被回收的。
1 | obj_property1 = null |
把obj_preperty1
设为null
,那么最原始的对象就真的没有了任何引用了。所以就可以被回收掉了。
什么时候算法会失效?
试试看下面的例子:1
2
3
4
5
6
7
8
9
10
11
12
13function example() {
var obj1 = {
property1: {
subpreperty1: 20
}
}
var obj2 = obj1.property1
obj2.property1 = obj1
return 'some random text'
}
example()
这个例子就可以看到,当函数执行完之后,obj1
和obj2
依然没有释放内存,因为它们互相调用,形成了回路。
回收的时机
1 | <html> |
执行以上测试代码发现,JS heap size会飙升到87MB,然后执行gc,但是并不是马上会回收掉内存,根据观察,每次回收的时间都不相同,这就有点奇怪了,有时候会几秒后就马上被回收,有时候等上一分钟才回收,可能和引擎自身的循环机制有关,这部分找不到相关的资料。
标记扫描算法
实例
这个算法会从根结点开始(也就是Javascript的全局对象)去遍历查找那些不能被访问到的对象。这个算法克服了引用计数回收算法的缺点。一个对象没有被引用将不能够被访问,不能被访问的对象也就是没有被引用。
1 | var obj1 = { |
如上图所示,我们的对象是能够被root
搜索访问到的
1 | obj1 = null |
现在可以看到,把obj1
设为null
之后,ROOT再也不能访问到这个对象,因此它会被回收掉。
这个算法从ROOT节点开始向下遍历,并标记所有它能够遍历到的对象节点,并且继续遍历这些节点的子节点。此过程一直持续到没有其他子节点或者路径可以遍历的时候才停止。
现在,垃圾回收器会忽略那些被标记的节点,把其它没有被标记的节点清掉。
可以看到,右面的节点子树是root节点开始遍历所遍历不到的,所以会被回收掉。