javascript垃圾回收

来源:互联网 发布:java将文字转换成语音 编辑:程序博客网 时间:2024/06/06 09:31

javascript具有自动的垃圾收集机制,可以使开发人员不用再去关心内存使用的问题。

一、标记清除

Javascript中最常用的垃圾收集方式是标记清除(mark-and-sweep)。当变量进入环境(例如,在环境中声明一个变量)时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进去相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。

可以使用任何方式来标记变量。比如,可以通过翻转某个特殊的位来记录一个变量何时进入环境,或者使用一个“进入环境的”变量列表及一个“离开环境的”变量列表来跟踪哪个变量发生了变化。说到底,如何标记变量其实并不重要,关键在于采取什么样的策略。

垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并收回它们所占用的空间。

二、引用计数

另一种不太常见的垃圾收集策略叫做引用计数(reference counting)。引用计数的含义是跟踪记录每一个值被引用的次数。当声明了一个变量并将一个引用类型值赋给另一个变量时,这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值得引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值得引用次数减1。当这个值得引用次数变为0时,说明没有办法再访问这个值了,因而就可以将其战友的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。

但是,引用计数有一个很大的问题,其无法解决循环引用。

function problem(){

var objectA = new Object();

var objectB = new Object();

objectA.someOtherObject = objectB;

objectB.anotherObject = objectA;

  }

在以上代码中,objectA和objectB通过各自的属性相互引用;也就是说,这两个对象的引用次数都是2,即永远不会为0,它们会在内存中永远存在。加入这个函数被反复调用,就会导致大量内存得不到回收。

三、分代回收

V8引擎中是通过分代回收(generational collection)来实现内存控制的。JavaScript对象是通多堆来进行分配的,在64位操作系统中,堆的大小为1.4G上下,在32位的操作系统中,其大小为0.7G左右。在node也可以通过命令重新设置堆的内存大小。分代回收的核心在于把内存分为新生代与老生代。新生代中的对象为存活时间较短的对象,老生代的对象为存活时间较长或常驻内存的对象。如下图所示。

新生代内存通过Scavenge算法进行垃圾回收,其具体实现是采用Cheney算法。即将新生代内存分为等分的两块,称为semispace(from)和semispace(to)。

      

在这两个semispace空间中,只有一个空间处在使用状态,另一个处于闲置状态。处在使用状态的空间称为semispace(from)。当为对象分配内存空间时,先是在from空间进行分配。

当进行垃圾回收时,会检查form空间中的存活对象,这些存活对象会被复制到to空间中,而非存活对象的空间将会被释放。完成复制后,from空间与to空间的角色发生调换。其缺点是只用到新生代空间中一半的内存。但是因为新生代中都是些生命周期比较短的对象,恰恰非常适用于这个算法。

当一个对象经过多次复制已然存活时,它将会被认为是生命周期较长的对象。这种较长生命周期的对象随后会被移动到老生代中,采用新的算法进行管理。对象从新生代中移动到老生代中的过程焦作晋升。

晋升有两种情况,如下图。

    

设置25%这个限制值的原因是当这次Scavenge回收完成后,这个to空间将变成from空间,接下来的内存分配将在这个空间内完成。如果占用比过高,会影响后续的内存分配。

在老生代内存空间中,使用Mark-Sweep&Mark-Compact进行垃圾收集。mark-sweep只清理死亡对象(区别于Scavenge的复制存活对象),但它清理死亡对象,释放其内存空间之后会存在内存空间不连续的情况,这些内存碎片会对后续的内存分配造成问题,如不能分配一个大的对象,这个时候就要提前触发垃圾回收,而这个时候的垃圾回收明显是不必要的。

是故,mark-compact被提了出来,他在清理完死亡对象的时候,将内存进行重新整理,死亡对象的内存往老生代内存空间的一端移动,那么剩下的内存就都将会是连续的,这个时候分配一个大的对象是没有问题的(除非内存已不够)。


0 0