垃圾回收机制(GC,Garbage Collection)负责在程序执行过程中回收无用的变量和内存占用的空间。一个对象虽然没有再次使用的可能,但是仍然存在于内存中的现象被称为内存泄漏。内存泄漏是非常危险的现象,尤其在长时间运行的程序中。如果一个程序出现了内存泄漏,它占用的内存空间就会越来越多,直至耗尽内存。
字符串、对象和数组没有固定的大小,所以只有当它们大小已知时才能对它们进行动态的存储分配。JavaScript程序每次创建字符串、数组或对象时,解释器都要分配内存才存储这个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便它们能够被再次利用;否则,JavaScript的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。
JS的垃圾回收有2种
标记清理:当变量进入上下文,比如在函数内部声明一个变量时,这个变量会被加上存在于上下文中的标记。而在上下文中的变量,逻辑上讲,永远不应该释放它们的内存,因为只要上下文中的代码在运行,就有可能用到它们。当变量离开上下文时,也会被加上离开上下文的标记。
给变量加标记的方式有很多种。比如,当变量进入上下文时,反转某一位;或者可以维护‘在上下文中’和‘不在上下文中’两个变量列表,可以把变量从一个列表转移到另一个列表。标记过程的实现并不重要,关键时策略。
垃圾回收程序运行的时候,会标记内存中储存的所有变量,然后会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了。随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存。
引用计数:其思路是对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为1.如果同一个值又被赋给另一个变量,那么引用数加1.类似地,如果保存对该值引用地变量被其他值给覆盖了,那么引用数-1.当一个值地引用数为0时,就说明没办法再访问到这个值了,因此可以安全地回收其内存了。垃圾回收程序下次运行的时候就会释放引用数为0的值的内存。
引用计数最早由Netscape Navigator 3.0采用,但很快就遇到了严重的问题:循环引用。所谓循环引用,就是对象A有一个指针指向对象B,而对象B也引用了对象A。清除循环引在代码使用结束以后设置为null就可以了。因为通过重新给A、B赋值改变了它们的引用关系。
V8的垃圾回收策略
副垃圾回收器,主要负责新生代的垃圾回收。
主垃圾回收器,主要负责老生代的垃圾回收。
副垃圾回收器主要负责新生区的垃圾回收,大多数小的对象最开始都会被分配在新生代中,该存储空间只有几十MB的大小,采用Scavenge 算法来处理把新生代空间对半划分为两个区域,一个是对象区域,一个是空闲区域。当对象区域快被写满时,首先要对对象区域中的垃圾做标记;标记完成之后,就进入垃圾清理阶段,副垃圾回收器会把这些存活的对象复制到空闲区域中,同时它还会把这些对象有序地排列起来,所以这个复制过程,也就相当于完成了内存整理操作,复制后空闲区域就没有内存碎片了。完成复制后,对象区域与空闲区域进行角色翻转,也就是原来的对象区域变成空闲区域,原来的空闲区域变成了对象区域。这样就完成了垃圾对象的回收操作,同时这种角色翻转的操作还能让新生代中的这两块区域无限重复使用下去。由于新生代中采用的 Scavenge 算法,所以每次执行清理操作时,都需要将存活的对象从对象区域复制到空闲区域。但复制操作需要时间成本,如果新生区空间设置得太大了,那么每次清理的时间就会过久,所以为了执行效率,一般新生区的空间会被设置得比较小。也正是因为新生区的空间不大,所以很容易被存活的对象装满整个区域。为了解决这个问题,JavaScript 引擎采用了对象晋升策略,也就是经过两次垃圾回收依然还存活的对象,会被移动到老生区中。
主垃圾回收器,主要负责老生代的垃圾回收。一些大的对象会直接被分配到老生区。因此老生区中的对象有两个特点,一个是对象占用空间大,另一个是对象存活时间长。老生代采用的是标记清除和标记整理,标记清除分为标记和清除2个阶段,标记阶段会遍历所有对象,对存活的对象进行标记未存活的对象进行回收,但是在标记清除之后的内存会产生很多不连续的碎片空间,这种不连续的碎片空间在遇到大对象时可能会由于空间不足而导致无法存储,为了解决内存碎片的问题提高对内存的利用就需要用到标记整理,标记整理算法相对于标记清除算法在回收阶段进行了改进,标记整理对待未标记的对象并不是立即进行回收,而是将存活的对象移动到一边,然后再清理。这种移动对象的操作相对而言是比较耗时的,所以执行速度上,比标记清除要慢。
需要主要的是由于JavaScript 是运行在主线程之上的,一旦执行垃圾回收算法,都需要将正在执行的JavaScript 脚本暂停下来,待垃圾回收完毕后再恢复脚本执行。我们把这种行为叫做全停顿(Stop-The-World)。
JS内存泄漏的如何检测?
当HEAP一直在升高就可以判断是内存泄漏了。