栈中JS引擎自动清除
栈内存中变量一般在它的当前执行环境结束就会被销毁被垃圾回收制回收,而堆内存中的变量则不会,因为不确定
其他的地方是不是还有一些对它的引用。堆内存中的变量只有在所有对它的引用都结束的时候才会被回收。
JS引擎可以通过逃逸分析辨别出哪些变量需要存储在堆上,哪些需要存储在栈上。
自动垃圾回收机制:找出不使用的值,释放内存。
函数运行结束,没有闭包或引用,局部变量被标记清除
全局变量:浏览器卸载页面被清除
垃圾回收算法
不论哪个垃圾回收算法,都有一套共同的流程:
1.标记内存空间中的活动对象(在使用中的对象)和非活动对象(可以回收的对象)。
2.删除非活动对象,释放内存空间。
3.整理内存空间,避免频繁回收后产生的大量内存碎片(不连续内存空间)。
引用计数
一个对象是否有被引用(循环引用导致内存泄露)
策略:跟踪每个变量被使用的次数
1.当声明了一个变量且将一个引用类型赋值给该变量时,这个值的引用次数为1
2.若同一个值又被赋给另一个值,引用数+1
3.如果该变量的值被其他的值覆盖了,则引用次数减1
4.当这个值的引用次数变为0的时候,说明没有变量在使用,这个值没法被访问了,回收空间,垃圾回收器会在运
行的时候清理掉引用次数为0的值占用的内存
缺点:
·需要一个计数器,所占内存空间大,因为我们也不知道被引用数量的上限。
·解决不了循环引用导致的无法回收问题。
标记清除
将“不再使用的对象”定义为“无法到达的对象”
工作流程:
1.垃圾收集器在运行时给内存变量加上标记,假设内存汇总所有对象都是垃圾,全标记为0
2.从根部出发,寻找可到达的变量,并将其标记清除,改为1
3.留有标记的变量就是待删除的,即标记为0,销毁并回收它们占用的内存
4.把所有内存中对象标记修改为0,等待下一轮垃圾回收
优点:实现简单,一位二进制位就可以为其标记
缺点:
内存碎片化,空闲内存块不连续
·分配速度慢,因为即使使用first-fit策略,其操作仍是一个O(n)的操作,最坏情况是每次都要遍历到最后,因为碎片化,
大对象的分配速率会更慢
复制算法
为了解决上述问题,复制算法出现了。
1.将整个空间平均分成from和to两部分。
2.先在from空间进行内存分配,当空间被占满时,标记活动对象,并将其复制到to空间。
3.复制完成后,将from和to空间互换。
由于直接将活动对象复制到另一半空间,没有了清除阶段的开销,所以能在较短时间内完成回收操作,并且每次复
制的时候,对象都会集中到一起,相当于同时做了整理操作,避免了内存碎片的产生。
优点:吞吐量高、没有碎片
缺点:首先,复制操作需要时间成本的,若堆空间很大且活动对象很多,则每次清理时间会很久;其次,将空间二
等分的操作,让可用的内存空间直接减少了一半。
标记整理
也叫做标记压缩算法。结合了标记-清除和复制算法的优点。
1.从一个GC root集合出发,标记所有活动对象。
2.将所有活动对象移到内存的一端,集中到一起。
3.直接清理掉边界以外的内存,释放连续空间。
该算法既避免了标记-清除法产生内存碎片的问题,又避免了复制算法导致可用内存空间减少的问题。当然,该算法
也不是没有缺点的,由于其清除和整理的操作很麻烦,甚至需要对整个堆做多次搜索,故而堆越大,耗时越多。
识别内存泄露
经验法则是,如果连续五次垃圾回收之后,内存占用一次比一次大,就有内存泄漏。
这就要求实时查看内存的占用情况。
在Chrome浏览器中,我们可以这样查看内存占用情况
1.打开开发者工具,选择Performance面板
2.在J顶部勾选Memory
3.点击左上角的record按钮
4在页面上进行各种操作,模拟用户的使用情况
5.一段时间后,点击对话框的stop按钮,面板上就会显示这段时间的内存占用情况
造成内存泄露
1.意外的全局变量
2.被遗忘的定时器和回调函数
3.事件监听没有移除
4.没有清理的DOM引用
5.子元素存在的内存泄漏

V8对GC优化

·栈中数据回收:执行状态指针ESP在执行栈中移动,移过某执行上下文,就会被销毁;
·堆中数据回收:V8引擎采用标记清除算法;
·V8把堆分为两个区域一新生代和老生代,分别使用副、主垃圾回收器;
·副垃圾回收器负责新生代垃圾回收,小对象(1~8M)会被分配到该区域处理;
·新生代采用scavenge算法处理:将新生代空间分为两半,一半空闲,一半存对象,对对象区域做标记,存活对
象复制排列到空闲区域,没有内存碎片,完成后,清理对象区域,角色反转;
·新生代区域两次垃圾回收还存活的对象晋升至老生代区域;
·主垃圾回收器负责老生区垃圾回收,大对象,存活时间长;
·新生代区域采用标记-清除算法回收垃圾:从根元素开始,递归,可到达的元素活动元素,否则是垃圾数据;
n
为了不造成卡顿,标记过程被切分为一个个子标记,交替进行。
分代式垃圾回收
以上所说的垃圾清理算法每次垃圾回收时都要检查内存中所有的对象,酱紫对一些大,老,存活时间长的对象来说,同
新,小,存活时间短的对象一个频率的检查很不好,因为前者需要时间长且不需要频繁进行清理,后者恰恰相反,如何
优化?
分代式
分代式机制把一些新、小、存活时间短的对象作为新生代,采用一小块内存频率较高的快速清理,而一些大、老、
存活时间长的对象作为老生代,使其很少接受检查,新老生代的回收机制及频率是不同的,可以说此机制的出现很
大程度提高了垃圾回收机制的效率
新老生代
V8的GC策略基于分代式垃圾回收机制,将堆内存分为新生代和老生代两区域,采用不同的垃圾回收策略进行垃圾回收
新生代的对象是存活时间较短的对象,简单来说就是新产生的对象,通常只支持1-8M的容量,而老生代的对象为存活时
间较长或常驻内存的对象,

新生代垃圾回收
新生代中对象一般存活时间较短,采用scavenge算法处理,在Scavenge算法具体实现中,主要采用一种复制式的方法
及Cheney.算法:
其将新生代空间对半分为from-space和to-space两个区域。新创建的对象都被存放到from-space,当空间快被写
满时触发垃圾回收。先对from-space中的对象进行标记,完成后将标记对象复制到to-space的一端,然后将两个
区域角色反转,就完成了回收操作。
由于每次执行清理操作都需要复制对象,而复制对象需要时间成本,所以新生代空间会设置得比较小(1~8M)。
当一个对象经过多次复制后依然存活,它将会被认为是生命周期较长的对象,随后会被移动到老生代中,采用老生
代的垃圾回收策略进行管理
另外还有一种情况,如果复制一个对象到空闲区时,空闲区空间占用超过了25%,那么这个对象会被直接晋升到老
生代空间中,设置为25%的比例的原因是,当完成Scavenge回收后,空闲区将翻转成使用区,继续进行对象内存
的分配,若占比过大,将会影响后续内存分配

老生代垃圾回收
老生代中的对象一般存活时间较长且数量也多,使用了两个算法,分别是标记清除算法和标记压缩算法。
什么情况下对象会出现在老生代空间中:

新生代中的对象是否已经经历过一次Scavenge算法,如果经历过的话,会将对象从新生代空间移到老生代空间
中。
·To空间的对象占比大小超过25%。在这种情况下,为了不影响到内存分配,会将对象从新生代空间移到老生代
空间中。
首先是标记阶段,从一组根元素开始,递归遍历这组根元素,遍历过程中能到达的元素称为活动对象,没有到达的
元素就可以判断为非活动对象
清除阶段老生代垃圾回收器会直接将非活动对象清理掉
前面我们也提过,标记清除算法在清除后会产生大量不连续的内存碎片,过多的碎片会导致大对象无法分配到足够
的连续内存,而V8中就采用了我们上文中说的标记整理算法来解决这一问题来优化空间
在老生代中,以下情况会先启动标记清除算法:
·某一个空间没有分块的时候
·空间中被对象超过一定限制
·空间不能保证新生代中的对象移动到老生代中
由于S是单线程运行的,意味着垃圾回收算法和脚本任务在同一线程内运行,在执行垃圾回收时,后续脚本任
务需要等垃圾回收完成后才能继续执行。若堆中的数据量非常大,一次完整垃圾回收的时间会非常长,导致应
用的性能和响应能力直线下降。为了避免垃圾回收影响应用的性能,V8将标记的过程拆分成多个子标记,让
垃圾回收标记和应用逻辑交替执行,避免脚本任务等待较长时间。