Chrome V8 垃圾回收机制
在JavaScript中,当我们创建变量(对象,字符串等)的时候,系统会自动给对象分配对应的内存, 当系统发现这些变量不再被使用的时候,会自动释放(垃圾回收)这些变量的内存。
为什么需要
在Chrome中,v8被限制了内存的使用(64位约1.4G/1464MB , 32位约0.7G/732MB)
- 表层原因是,V8最初为浏览器而设计,不太可能遇到用大量内存的场景
- 深层原因是,V8的垃圾回收机制的限制(如果清理大量的内存垃圾是很耗时间,这样回引起JavaScript线程暂停执行的时间,那么性能和应用直线下降)
Chrome 垃圾回收算法
在JavaScript中,其实绝大多数的对象存活周期都很短,大部分在经过一次的垃圾回收之后,内存就会被释放掉,而少部分的对象存活周期将会很长,一直是活跃的对象,不需要被回收。为了提高回收效率,V8 将堆分为两类新生代
和老生代
,新生代中存放的是生存时间短的对象,老生代中存放的生存时间久的对象。 ===> 分代式垃圾回收机制
- 新生代:生存时间较短的对象 ===> 空间小 ===> 1-8Mb ===> 副的垃圾回收器
- 老生代:生存时间较长的对象 ===> 空间大 ====> 主的垃圾回收器
- V8 分别使用两个主/副的垃圾回收器,以便更高效地实施垃圾回收。
新生代垃圾回收
新生代的垃圾回收算法主要通过 复制(Scavenge) 算法
进行垃圾回收,该算法的流程主要是:
将堆内存一分为二,一个处于使用中 (from),一个处于闲置中 (To)。
分配内存时,会先分配到使用中的内存(from 空间),当开始进行垃圾回收的时候,回收生命周期结束的对象。
过程:
- 标记活动对象和非活动对象
- 复制 from space 的活动对象到 to space 并对其进行排序
- 释放 from space 中的非活动对象的内存
- 将 from space 和 to space 角色互换
对象晋升策略
一般在新生代申请内存,当经历一次 GC 之后如果对还存活,那么对象的年龄 +1。当年龄超过一定值(默认是 15,可以通过参数 -XX:MaxTenuringThreshold 来设定)后,如果对象还存活,那么该对象会进入老生代内存空间中(也就是晋升)。
在上面新生代垃圾回收过程中,并不是每一次都会把对象复制到闲置空间(To)中,而是在复制之前,会先判断两个条件,如果满足条件,则会触发对象晋升,而不是复制到闲置空间中。
这两个条件分别是:
- 是否经历过 Scavenge(复制) 回收(主要是通过内存地址判断)
- To 空间的内存占用比是否超过限制/回收次数 有没有到限制值 。
两个条件达成其一即可,对象晋升后,会在老生代中被新的回收算法处理。
老生代垃圾回收
在老生代中,存活对象通常占的比重较大,所以复制的效率会相对较低,不适用 Scavenge 算法。 V8 中老生代的垃圾回收主要采用了Mark-Sweep (标记 - 清除)
和 Mark-Compact (标记 - 整理)
相结合的方式。
Mark-Sweep(标记 - 清楚)
顾名思义,Mark-Sweep 叫做标记 - 清除,它分为两个阶段:标记和清除。
- 标记阶段,
Mark-Sweep
对老生代进行第一次扫描,标记活动对象 - 清除阶段,
Mark-Sweep
对老生代进行第二次扫描,清除未被标记的对象,即清理非活动对象
可以对比出,Scavenge 只复制活着的对象,而 Mark-Sweep 只清理死亡对象。所以前者适合(复制场合少,即存活少的区域)新生代,后者适合(清理场合少,即存活多的区域)老生代。
但是 Mark-Sweep 有一个比较严重的问题,就是清除后可能会有很多内存碎片,造成这些内存空间无法合理的利用,要消除内存碎片,就需要进行内存整理。
Mark-Compact(标记 - 整理)
- Mark-Compact 就是为了解决 Mark-Sweep 的问题而提出的。
- Mark-Compact,即标记 - 整理,是在标记 - 清除的基础上演变来的。它和 Mark-Sweep 的差别在于,当对象在标记为死亡后,在整理的过程中,将活着的对象往一端移动(整理),移动完成后,直接清理边界外的内存。
- 因为 Mark-Compact 需要移动对象再进行清除,很明显,它比 Mark-Sweep 要慢。
V8 的策略
通过上面三种算法,我们可以得出一个对比表格:
Mark-Compact 慢,Mark-Sweep 有碎片,而 V8 在取舍上结合两种:主要使用 Mark-Sweep,当空间不足以分配空间给新生代中晋升的对象时,使用 Mark-Compact 消除内存碎片。
总结
谷歌浏览器 现在是采用 分代式垃圾回收机制, 将对象分为两部分:新生代与老生代。并且对两部分使用不同的算法
- 新生代使用
复制(Scavenge) 算法
进行垃圾回收,这种算法牺牲空间来换取时间,实际上只利用了新生代一半的空间进行存储。但优点在于面对生命周期短的对象时速度较快,而且不会产生内存碎片。 - 新生代中的对象 经历过多次GC之后,对象依然存在,就会进行对该对象晋升,将其 移动到老生代区域。 老生代使用
Mark-Sweep(标记 - 清楚)
及Mark-Compact(标记-整理)
两种结合。主要使用 标记清楚(Mark-Sweep),当碎片太多、不足以分配一块内存给晋升的对象时,就利用 标记整理(Mark-Compact) 进行整理。