首先G1的gc可以分为三个种类
-
新生代gc(young gc)
与其他收集器的新生代gc类似,也是采用标记-复制-清除算法 -
并发标记周期
与CMS GC过程类似2.1 初始标记
这个阶段是标记根对象能直达的对象,会产生STW,伴随着一次young gc2.2 根区域扫描
由于初始标记必然会伴随一次新生代GC,所以在初始化标记后,eden被清空,并且存活对象被移到survivor区。在这个阶段,将扫描由survivor区直接可达的老年代区域,并标记这些直接可达的对象。 这个过程是可以和应用程序并发执行的。但是根区域扫描不能和新生代GC同时发生(因为根区域扫描依赖survivor区的对象,而新生代GC会修改这个区域),故如果恰巧此时需要新生代GC,GC就需要等待根区域扫描结束后才能进行,如果发生这种情况,这次新生代GC的时间就会延长2.2 并行标记
和CMS类似,并发标记将会扫描并查找整个堆的存活对象,并做好标记。这是一个并发过程,并且这个过程可以被一次新生代GC打断。2.3 重新标记
和CMS一样,重新标记也是会使应用程序停顿,由于在并发标记过程中,应用程序依然运行,因此标记结果可能需要修正,所以在此阶段对上一次标记进行补充。在G1中,这个过程使用SATB(Snapshot-At-The-Begining)算法完成,即G1会在标记之初为存活对象创建一个快照,这个快照有助于加速重新标记的速度。2.4 清理
清理阶段主要做的事情是,清理没有存活对象的region区域,将其添加到free列表中,并将剩下的region区域按照垃圾的多少从多到少排序,为后面的CSet选择做准备SATB 利用 write barrier 将所有即将被删除的引用关系的旧引用记录下来,最后以这些旧引用为根 Stop The World 地重新扫描一遍即可避免漏标问题。 因此G1 Remark阶段 Stop The World 与 CMS了的remark有一个本质上的区别,那就是这个暂停只需要扫描有 write barrier 所追中对象为根的对象, 而 CMS 的remark 需要重新扫描整个根集合,因而CMS remark有可能会非常慢。
-
混合gc(Mixed gc)
在上一步的并行标记过程后,若满足mixed gc的条件,这时会接着执行一次或多次mixed gc。
mixed gc的过程与young gc的过程完全一致,只是回收的区域不仅仅是新生代区域,也包含了并行标记过程中满足条件的region(CSet)
eden区被占满会触发新生代gc,由于伴随着对象的晋升,可能会触发并发标记周期,并发标记周期完成后,会触发混合gc,当一次或多次混合gc执行完之后,有可能又会触发新生代gc。
-
Full gc
G1的Full gc是serial模式,所以非常慢4.1 并发模式失效
G1启动并发标记周期,但是在混合gc之前,老年代就被填满了,这时候G1就会放弃标记周期,改为执行Full gc,对应的gc日志为:[GC concurrent-mark-abort]
解决办法:发生这种失败意味着堆的大小应该增加了,或者G1收集器的后台处理应该更早开始,或者需要调整周期,让它运行得更快(如,增加后台处理的线程数)。4.2 晋升失败
G1在进行新生代gc时,老年代没有足够的内存提供给晋升对象,将会触发Full gc。对应的gc日志为:to-space exhausted
解决这种问题的方式是:
a. 增加 -XX:G1ReservePercent 选项的值(并相应增加总的堆大小),为“目标空间”增加预留内存量。
b. 通过减少 -XX:InitiatingHeapOccupancyPercent 提前启动标记周期。
c. 也可以通过增加 -XX:ConcGCThreads 选项的值来增加并行标记线程的数目。
4.3 疏散失败
进行新生代gc时,survivor和老年代没有足够的空间容纳存活的对象。对应的gc日志为: to-space overflow
解决办法与4.2一样。4.4 巨型对象分配失败
巨型对象分配失败也会触发Full gc,解决办法:增大regionSize4.5 metaspace gc
metaspace大小达到阈值(metaspaceSize大小,是动态的),会触发Full gc