V8内存限制
存在限制:Chrome限制了v8内存的使用,(64位约1.4G/1464MB , 32位约0.7G/732MB),这将导致无法直接操作大内存对象。
限制原因:①表层原因为V8最初为浏览器而设计,不太可能遇到用大量内存的场景,②深层原因是V8的垃圾回收机制的限制,(如果清理大量的内存垃圾是很耗时间,这样回引起JavaScript线程暂停执行的时间,那么性能和应用直线下降)
V8垃圾回收机制算法
V8内存分代
Scavenge算法
通过复制的方式实现的垃圾回收算法。(简单理解--只复制活着的对象)它将堆内存分为两个 semispace,一个处于使用中(From空间),另一个处于闲置状态(To空间)。当分配对象时,先是在From空间中进行分配。当开始进行垃圾回收时,会检查From空间中的存活对象,这些存活对象将被复制到To空间中,而非存活对象占用的空间将会被释放。完成复制后,From空间和To空间的角色发生对换。在垃圾回收的过程中,就是通过将存活对象在两个 semispace 空间之间进行复制。
年轻分代中的对象有机会晋升为年老分代,条件主要有两个:
①对象是否经历过Scavenge回收:
②To空间的内存占用比超过限制:
设置25%这个限制值的原因是当这次Scavenge回收完成后,这个To空间将变成From空间,接下来的内存分配将在这个空间中进行。如果占比过高,会影响后续的内存分配。
Mark-Sweep & Mark-Compact 算法
对于年老分代中的对象,由于存活对象占较大比重,再采用上面的方式会有两个问题:一个是存活对象较多,复制存活对象的效率将会很低;另一个问题依然是浪费一半空间的问题。为此,V8在年老分代中主要采用了Mark-Sweep(标记清除)标记清除和Mark-Compact(标记整理)相结合的方式进行垃圾回收。
①Mark-Sweep(标记清除)
mark-sweep 分为两个阶段,标记和清除阶段。在标记阶段中遍历堆中所有对象,并标记活着的对象,在随后的清除阶段中,只清除没有标记的对象。(简单理解--只清理死亡对象)
mark-sweep内存碎片问题是有清理死亡对象后,内存空间出现不连续的状态,这种内存碎片会对后续的内存分配造成问题,因为很可能出现需要分配一个大对象的情况,这是所有的碎片空间都无法完成此次分配。为了解决这个碎片问题提出了mark-compact
②Mark-Compact(标记整理)
mark-compact对于标记死亡对象的整理过程中,将活着的对象往一端移动,移动完成后,直接清理掉边界外的内存。(简单理解--整理活着对象向一段移动,另一端直接清空)
上诉三种垃圾回收算法的简单对比
回收算法 | Mark-Sweep | Mark-Compact | Scavenge |
速度 | 中等 | 最慢 | 最快 |
空间开销 | 少(有碎片) | 少(无碎片) | 双倍空间(无碎片) |
是否移动对象 | 否 | 是 | 是 |
Incremental Marking (增量标记)
由于对象复制/标记清除/标记整理的回收期间内,V8会暂停所有正在执行业务的代码,待执行完成垃圾回收后再恢复执行业务代码,这被称为--“全停顿”。这种“全停顿”,如果随着堆大小的增长,停顿的时间会急速增加,那么会造成停顿的现象非常厉害。因此V8在标记期间,采用了增量标记的方法,将标记的过程拆分成很多小“步进”,每做完一“步进”就让务代码的执行一小会,再标记,形成循环交替执行标记直到标记阶段完成。这样把原来应用程序停顿的整个时间就会变分拆成多个细小的时间片,极大的提高了应用程序的响应度。
Lazy sweeping(惰性清除)
其它优化
V8已经加入了并行清除,主线程不会操作已死对象,由独立的线程来负责回收死对象的内存,整个过程只需要非常少量的同步操作。同时V8正在实验并行标记,并将在今后引入这一技术。
总结
《垃圾回收的算法与实现》《垃圾回收算法手册 自动内存管理的艺术》等书籍