浏览器的垃圾回收机制
Javascript 具有自动垃圾回收机制(GC:Garbage Collection),也就是说,执行环境会负责管理代码执行过程中使用的内存。,其原理是垃圾收集器会周期性的去查找没有引用的变量,然后释放其内存。但垃圾回收机制并不是实时的,因为它的开销过大,并且它运行时是不能响应任何操作的。所以垃圾回收机器会按照固定的时间间隔,周期性的去执行。
1.垃圾回收机制的作用
思考:我们平时定义变量,或者说new一个对象,都会申请内存。那么内存怎么释放呢?
1、栈中的内存会自动释放(cpu在执行每一行代码时,会使用调用栈。每次调用一个函数会有压栈,函数调用完毕,会有弹栈,弹栈时,就会释放掉栈中的内存)。
2、堆中的内存,不会自动释放:
1)、之前的C语言和C++需要程序员自己写代码(调用函数的方式)释放内存。
2)、后来的java,C#,js 等等都专门提供了一个垃圾回收的程序,释放内存。
2.垃圾回收机制
垃圾回收机制怎么知道,哪些内存不再需要呢?
垃圾回收有两种方法:标记清除、引用计数。
引用计数不太常用,标记清除较为常用。
1、标记清除
这是javascript中最常用的垃圾回收方式。当变量进入执行环境是,就标记这个变量为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到他们。当变量离开环境时,则将其标记为“离开环境”。
垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。
2、引用计数
另一种不太常见的垃圾回收策略是引用计数。引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。
引用计数有个最大的问题: 循环引用。
比如对象A有一个属性指向对象B,而对象B也有有一个属性指向对象A,这样相互引用。
function fun() {
let obj1 = {};
let obj2 = {};
obj1.a = obj2;// obj1 引用 obj2
obj2.a = obj1;// obj2 引用 obj1
}
//obj1和obj2通过各自的属性相互引用;也就是说这两个对象的引用次数都是2。
//在采用引用计数的策略中,由于函数执行之后,这两个对象都离开了作用域,函数执行完成之后,
//obj1和obj2还将会继续存在,因为他们的引用次数永远不会是0。
//这样的相互引用如果说很大量的存在就会导致大量的内存泄露。
//解决方案==========手动解除引用
obj1.a=null;
obj2.a=null;
3.GC的缺陷
和其他语言一样,javascript的GC策略也无法避免一个问题:GC时,停止响应其他操作,这是为了安全考虑。而Javascript的GC在100ms甚至以上,对一般的应用还好,但对于JS游戏,动画对连贯性要求比较高的应用,就麻烦了。这就是新引擎需要优化的点:避免GC造成的长时间停止响应。
4.GC优化策略
分代回收(Generation GC)
这个和Java回收策略思想是一致的,也是V8所主要采用的。目的是通过区分“临时”与“持久”对象;多回收“临时对象”区(young generation),少回收“持久对象”区(tenured generation),减少每次需遍历的对象,从而减少每次GC的耗时。
增量GC
“每次处理一点,下次再处理一点,如此类推”。虽然耗时短,但中断较多,带来了上下文切换频繁的问题。