正文
本文主要介绍几种垃圾收集器。
垃圾收集器
我们都知道垃圾收集算法有四种:标记-复制、标记-清除、标记-整理和分代理论。那么对应的实现有哪些了,我们意义看下它们的基本理论和优缺点。
1.1:Serial收集器(-XX:+UseSerialGC -XX:+UseSerialOldGC)
这是一个单线程收集器,优点是简单高效(与其它收集器的单线程比较),但是缺点也很明显,就是STW的时间会很长,因为只有一个线程进行垃圾收集,效率相对来说,肯定会更低。
Serial Old收集器是Serial收集器的老年版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方案。
新生代采用复制算法,老年代采用标记-整理算法。
1.2:Parallel Scavenge收集器(-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代))
这个收集器是Serial收集器的多线程版本。它的注重点是提高吞吐量(CPU中用于运行用户代码的时间与CPU总消耗时间的比值)。CMS等其它收集器的重点是用户线程的停顿时间(提高用户体验)。
Parallel Old收集器是Parallel Scavenge收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器(JDK8默认的新生代和老年代收集器)。
新生代采用复制算法,老年代采用标记-整理算法。
1.3:ParNew收集器(-XX:+UseParNewGC)
ParNew收集器其实跟Parallel收集器很类似,区别主要在于它可以和CMS收集器配合使用。
新生代采用复制算法,老年代采用标记-整理算法。
流程图和Parallel收集器一样。
1.4:CMS收集器(-XX:+UseConcMarkSweepGC(old))
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它第一次实现了垃圾收集线程与用户线程(基本上)同时工作。
通过名字中的 Mark Sweep 可以知道CMS是采用 标记-清除 算法实现的,它的实现步骤相对于前面几种也更复杂。整个过程分为四个步骤:
- 初始标记:暂停所有的其他线程(STW),并记录下 gc roots 能直接引用的对象,速度很快。
- 并发标记:并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程, 这个过程耗时较长但是不需要停顿用户线程, 可以与垃圾收集线程一起并发运行。因为用户程序继续运行,可能会有导致已经标记过的对象状态发生改变。
- 重新标记:重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。主要用到三色标记里的增量更新算法(见下面详解)做重新标记。
- 并发清理:开启用户线程,同时GC线程开始对未标记的区域做清扫。这个阶段如果有新增对象会被标记为黑色不做任何处理(见下面三色标记算法详解)。
-
并发重置:重置本次GC过程中的标记数据。
CMS收集器的优点很明显:并发收集,低停顿。但是它的缺点也很明显: - 对CPU资源很敏感;
- 无法处理浮动垃圾(在并发标记和并发清理阶段又产生垃圾,这种浮动垃圾只能等到下一次gc再清理了);
- 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生,当然通过参数-
XX:+UseCMSCompactAtFullCollection可以让jvm在执行完标记清除后再做整理; - 执行过程中的不确定性,有可能发生第一次GC还没有进行完,第二次GC又开始了。特别是在并
发标记和并发清理阶段会出现,一边回收,系统一边运行,也许没回收完就再次触发full gc,也就是"concurrent mode failure",此时会进入stop the world,用serial old垃圾收集器来回收。
三色标记
把Gcroots可达性分析遍历对象过程中遇到的对象,按照“是否访问过”这个条件标记成一下三种颜色:
- 黑色:表示对象已经被垃圾收集器访问过, 且这个对象的所有引用都已经扫描过。 黑色的对象代表已经扫描过, 它是安全存活的, 如果有其他对象引用指向了黑色对象, 无须重新扫描一遍。 黑色对象不可能直接(不经过灰色对象) 指向某个白色对象。
- 灰色:表示这个对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用没有被扫描过。
- 白色:表示这个对象还没有被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段, 所有的对象都是白色的, 若在分析结束的阶段, 仍然是白色的对象, 即代表不可达。
多标-漏标
在并发标记阶段,因为标记期间应用线程还在运行,对象间的引用关系可能发生变化,多标和漏标的情况就有可能发生。
多标-浮动垃圾
在并发标记阶段,之前扫描过的Gcroots被销毁,这个Gcroots引用的对象又被扫描过,那么本轮GC不会回收这部分对象内存。这部分本身应该回收的内存称为浮动垃圾。但是它并不会影响垃圾回收的正确性,所以可以等到下次GC再清除。
另外,针对并发标记和并发清除阶段新增的对象,通常做法是直接指定为黑色,本轮GC不做处理。
漏标
漏标的一种可能场景:并发标记的时候,D还没有被扫描到,B指向D的引用被清除,这时候D任然是白色的,然后将D指向A的成员属性,但是因为A是黑色,不会被重新扫描,所以D还是白色,会被清除。但是这种情况是肯定不允许的,必须解决,有两种解决方案:增量更新(Incremental Update) 和原始快照(Snapshot At The Beginning,SATB) 。
增量更新是指在并发标记阶段,如果黑色对象有新增白色对象的引用,会将这个引用记录下来,等到重新标记阶段,在以这个黑色对象为跟,进行重新扫描,这时候,这些白色对象就会标记成灰色或者黑色。简单理解的话,就是黑色对象一旦插入了新的白色对象引用,它就会变成灰色。
原始快照是指在清除灰色对象和白色对象的引用关系时,会将删除的引用记录下来。在并发标记结束后,会将这些白色对象直接标记为黑色,保证它们不会再本轮GC被清除。
记忆集和卡表
在可达性分析中,对于夸代引用,hotspot会使用一种叫做“卡表”(cardtable)的方式来实现记忆集来记录夸代引用关系。