1、基础概念
(1)js内存的生命周期
-1- 内存分配:当声明变量、函数、对象时,系统会自动分配内存给它们
-2- 内存使用:即读写内存,也就是使用变量、函数
-3- 内存回收:使用完毕,由垃圾回收器自动回收不再使用的内存
(2)垃圾回收机制(Garbage Collection)简称GC
js的垃圾回收机制是一种内存管理技术,对开发者来说,js的垃圾回收机制是自动的、无形的。
我们创建的原始值、对象、函数……这一切都会占用内存,而在js引擎中有一个垃圾回收器在后台执行。
注意:全局变量一般不会回收(关闭页面时回收);一般情况下局部变量的值,不用了,会被自动回收掉。
2、已过时的垃圾收集算法 - 引用计数法
(1)引用计数法原理
主要看当前创建的对象在堆内存中有没有被其他对象引用,如果没有被其他对象引用,那么该对象将被垃圾回收机制回收。
let obj1 = { name: 'test'}; // 创建一个对象,被obj1所引用,计数为1
let obj2 = obj1; // obj2变量是第二个对该对象的引用,计数为2
obj1 = null; // 该对象的原始引用obj1已经没有了,计数为1
obj2 = null; // 此时对象所有引用都没有了,计数为0,垃圾回收机制回收该对象
(2)致命缺点:循环引用
两个对象内的属性相互引用对象,两个对象的引用计数永远大于0,无法回收导致内存泄漏。
function func(){
let obj1 = {};
let obj2 = {};
obj1.a = obj2;
obj2.a = obj1;
return 1;
}
在2012年以后所有现代浏览器都取消这种算法了,取而代之的是标记清除法。
3、标记清除法(mark-and-sweep)
2012年起,所有现代浏览器都使用了标记清除法
(1)标记清除法原理
它会定期执行以下“垃圾回收”步骤:
例如,对象有如下的结构:
第一步,标记所有的根:
下面列出固有的可达值的基本集合, 这些值明显不能被释放。
比方说:
->当前执行的函数,它的局部变量和参数。
->当前嵌套调用链上的其他函数、它们的局部变量和参数。
->全局变量。
->(还有一些内部的)
这些值被称作 根 (roots)
第二步,跟随根的引用标记它们所引用的对象:
第三步,……如果还有引用的话,继续标记:
最后,通过这个过程没有被标记的对象被认为是不可达的,并且会被删除。
关于垃圾回收,js引擎做了许多优化,使垃圾回收运行速度更快,也使得现代浏览器的性能越来越高,并且不会对代码执行引入任何延迟。
(2)垃圾收集机制的一些优化建议
分代收集(Generational collection)—— 对象被分成两组:“新的”和“旧的”。在典型的代码中,许多对象的生命周期都很短:它们出现、完成它们的工作并很快死去,因此在这种情况下跟踪新对象并将其从内存中清除是有意义的。那些长期存活的对象会变得“老旧”,并且被检查的频次也会降低。
增量收集(Incremental collection)—— 如果有许多对象,并且我们试图一次遍历并标记整个对象集,则可能需要一些时间,并在执行过程中带来明显的延迟。因此,引擎将现有的整个对象集拆分为多个部分,然后将这些部分逐一清除。这样就会有很多小型的垃圾收集,而不是一个大型的。这需要它们之间有额外的标记来追踪变化,但是这样会带来许多微小的延迟而不是一个大的延迟。
闲时收集(Idle-time collection)—— 垃圾收集器只会在 CPU 空闲时尝试运行,以减少可能对代码执行的影响。