(一)摘要
v8的垃圾回收机制
(二)前提知识
垃圾回收【主要】有两个方式,一个是标记清除,一个是引用计数。但都有缺点:
标记清除
标记法就是,将可达的对象标记起来,不可达的对象当成垃圾回收
问题来了,可不可达,通过什么来判断呢?
根据可达性来判断,可达性就是从初始的根对象(window或者global)的指针开始,向下搜索子节点,搜索到了,说明可达,没有子节点,就证明是需要被释放的一个对象。(向下搜索子节点,子节点被搜索到了,说明该子节点的引用对象可达,并为其进行标记,然后接着递归搜索,直到所有子节点被遍历结束。那么没有被遍历到节点,也就没有被标记,也就会被当成没有被任何地方引用,就可以证明这是一个需要被释放内存的对象,可以被垃圾回收器回收。)
缺点:最大的问题是在进行一次标记清除回收后,内存空间会出现不连续的状态。这种内存碎片会对后续的内存分配造成问题,因为很可能出现需要分配一个大对象的情况,这时所有的碎片空间都无法完成此次分配,就会提前触发垃圾回收,而这次回收是不必要的。
引用计数
对引用类型的值技术,不是为变量计数。
就是判断一个对象的引用数,引用数为0 就回收,引用数大于0 就不回收。
缺点:但是两个对象循环引用的话就完蛋了。由于他们互相引用,各自引用数都是 1,所以不会被回收,从而造成内存泄漏。
JS的内存管理
由于栈内存所存的基础数据类型大小是固定的,所以栈内存的内存都是操作系统自动分配和释放回收的。
由于堆内存所存大小不固定,系统无法自动释放回收,所以需要 JS引擎来手动释放这些内存
为啥要垃圾回收
在 Chrome 中,V8 被限制了内存的使用(64位约1.4G/1464MB , 32位约0.7G/732MB),为什么要限制呢?
表层原因:V8 最初为浏览器而设计,不太可能遇到用大量内存的场景
深层原因:V8 的垃圾回收机制的限制(如果清理大量的内存垃圾是很耗时间,这样会引起 JavaScript 线程暂停执行的时间,那么性能和应用直线下降)
前面说到栈内的内存,操作系统会自动进行内存分配和内存释放,而堆中的内存,由 JS 引擎(如 Chrome 的 V8)手动进行释放,当我们的代码没有按照正确的写法时,会使得 JS 引擎的垃圾回收机制无法正确的对内存进行释放(内存泄露),从而使得浏览器占用的内存不断增加,进而导致 JavaScript 和应用、操作系统性能下降。
(三)v8 的垃圾回收机制
而v8引擎则主要采用了分代式回收机制,根具对象存活时间进行分代,将内存分为新生代和老生代两块内存空间。
- 新生代主要保存存活时间较短的对象,新建的对象也优先分配到新生代中。
- 老生代主要保存存活时间较长的对象。
他们采用不同的算法进行垃圾回收。
- 新生代采用 Scavenge(翻译清道夫) 算法。
- 老生代采用Mark-sweep(翻译清扫)(标记清除) 和 Mark-compact(翻译把…紧压在一起)(标记整理) 算法。
3.1 新生代 scavenge 算法
在分代的基础上,新生代主要采用清道夫(scavenge )算法,清道夫算法里面又主要采用了 Cheney 算法。
它是一种采用复制的方式实现垃圾回收的算法,将新生代的空间一分为二,每一部分空间称为 semispace(半空间)。
在这两个空间中,只有一个处于使用状态:from空间,另一个处于闲置状态:to空间
在分配对象的时候,v8会先在
当我们分配对象时,先在 From 空间进行分配。当开始进行垃圾回收时,会检查 From 空间中的存活对象,这些存活对象将被复制到 To 空间中,而非存活对象占用的空间将会被释放。
完成复制后,From 空间和 To 空间的角色发生对换,称为翻转。
优点:scavenge 算法只会复制存活的对象,并且对于生命周期短的场景,他的存活对象只占少数,所以在时间效率上有优秀的表现。
缺点:他只利用了堆内存空间的一半,不适用于大规模的垃圾回收场景,是一个牺牲空间换取时间的算法。
3.2 用于老生代的算法,标记整理(Mark-Conpact)与标记清除(Mark-Sweep)
当一个对象经过多次复制还存活的话,就会被放到老生代里去,这个过程叫晋升。
晋升的两个条件:
1、对象在新生代期间是否经历过 Scavenge 回收;
2、使 To 空间的内存占用比超过限制(To 空间内存消耗是否超过 25%,如果超过对象直接晋升)。
在老生代中,使用标记清除与标记整理算法对对象进行垃圾回收。
标记清除
标记存活的对象,清除没标记的对象。
缺点:就像上面说的,标记清除最大的问题在于内存空间会变成不连续的样子,这时所有的碎片空间都无法完成此次分配,就会提前触发垃圾回收,而这次回收是不必要的。
为了解决这个问题,标记整理被提了出来
标记整理
它是在标记清除的基础上发展而来,就是在对象被标记完之后,将活着的对象移动到一起,然后清除边界以外的空间。
两者关系
标记整理与标记清楚的关系只是一种策略递进的关系。当空间不足以对新生代晋升过来的对象进行分配时,才会使用标记整理。
(四)全停顿问题
要是js代码运行与垃圾回收同时进行了怎么办。
JS 代码的运行要用到 JS 引擎,垃圾回收也要用到 JS 引擎,那如果这两者同时进行了,发生冲突了咋办呢?答案是,**垃圾回收优先于代码执行,会先停止代码的执行,等到垃圾回收完毕,再执行 JS 代码。**这个过程,称为全停顿。
所以有些场景活动对象比较多的时候,就“卡”了
解决:
orinoco 为 V8 的垃圾回收器的项目代号,为了提升用户体验,解决全停顿问题,它提出了增量标记、懒性清理、并发、并行的优化方法。