Garbage First
The Garbage First Garbage Collector (G1 GC) is the low-pause, server-style generational garbage collector for Java HotSpot VM. The G1 GC uses concurrent and parallel phases to achieve its target pause time and to maintain good throughput. When G1 GC determines that a garbage collection is necessary, it collects the regions with the least live data first (garbage first).
特点
- 并发收集
- 逻辑分区,物理不分区
- 压缩空闲空间不会延长GC的暂停时间
- 更易预测的GC暂停时间
- 适用不需要实现很高的吞吐量的场景
G1堆内存模型 Region
- 在G1收集器出现之前的所有他收集器,包括CMS在内,垃圾收集的目标范围要么是整个新生代(Minor GC),要么就是整个老年代(Major GC),再要么就是整个Java堆(Full GC)。而G1跳出了这个樊笼,它可以面向堆内存任何部分来组成回收集(Collection Set,一般简称CSet)进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式。
- G1开创的基于Region的堆内存布局是它能够实现这个目标的关键。虽然G1也仍是遵循分代收集理论设计的,但其堆内存的布局与其他收集器有非常明显的差异:G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。
- Region中还有一类特殊的Humongous区域,专门用来存储大对象。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围为1MB~32MB,且应为2的N次雾。而对于那些超过了整个Region容量的超级大对象,将会被存放在N个连续的Humongous Region之中,G1的大多数行为都把Humongous Region作为老年代
的一部分来进行看待,如图3-12所示。 - 虽然G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它们都是一系列区域(不需要连续)的动态集合。G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作为单次回收的最小单元,即每次收集到的内存空间都是Region大小的整数倍,这样可以有计划地避免在整个Java堆中进行全区域的垃圾收集。更具体的处理思路是让G1收集器去跟踪各个Region里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间(使用参数-XX:MaxGCPauseMillis指定,默认值是208gm),优先处理回收价值收益最大的那些Reqon,这也就是“Gathage First”名字的时间内获取尽可能高的收集效率。
G1收集器运作过程 Mixed GC
- 初始标记(InitialMarking):仅仅只是标记一下GCRoots能直接关联到的对象,并且修改TAMS
指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要
停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际
并没有额外的停顿。 - 并发标记(Concurrent Marking):从GCRoot开始对堆中对象进行可达性分析,递归扫描整个堆
里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以
后,还要重新处理SATB记录下的在并发时有引用变动的对象。 - 最终标记(FinalMarking):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留
下来的最后那少量的SATB记录。 - 筛选回收(Live Data Counting and Evacuation):负责更新Region的统计数据,对各个Region的回
收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region
构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧
Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行
完成的。
从上述阶段的描述可以看出,G1收集器除了并发标记外,其余阶段也是要完全暂停用户线程的,换言之,它并非纯粹地追求低延迟,官方给它设定的目标是在延迟可控的情况下获得尽可能高的吞吐量,所以才能担当起“全功能收集器”的重任与期望。
RSet
RememberedSet
- 记录了其他Region中的对象到本Region的引用
- RSet的价值在于使得垃圾收集器不需要扫描整个堆找到谁引用了当前分区中的对象,只需要扫描RSet即可。
三色标记
白色:表示对象尚未被垃圾收集器访问过。
黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。
灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没被扫描过。
漏标问题
本来是live object,但是由于没有被垃圾收集器访问过,被当成garbage回收掉了。
漏标的充分必要条件:
- 标记进行时增加了一个黑到白的引用,如果不重新对黑色进行处理,则会漏标。
- 标记进行时删除了灰色对象到白色对象的引用,那么这个白色对象可能被漏标。
打破上述两个条件之一即可解决漏标问题:
- incremental update - 增量更新(CMS)
关注引用的增加,把黑色重新标记为灰色,下次重新扫描属性 - SATB snapshot at the beginning - 关注引用的删除(G1)
当灰色引用到白色引用消失时,要把这个引用推到GC的堆栈,保证白色对象还能被GC扫描到.
G1采用SATB是因为,当灰色指向白色的引用消失时,如果没有黑色指向白色。引用会被push到堆栈。下次扫描时拿到这个引用,由于有RSet的存在,不需要扫描整个堆去查找指向白色的引用,效率较高。SATB和Rset搭配浑然天成。
G1有Full GC么?
会,对象分配不下的时候。
如果G1产生FGC,你应该做什么?
- 扩内存
- 提高CPU性能(回收的快,业务逻辑产生对象的速度固定,垃圾回收越快,内存空间越大)
- 降低MixedGC触发的阈值,让MixedGC提早发生(默认是45%)
参考资料
- Garbage First Garbage Collector Tuning
- 《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)周志明》
- 3.4.6 并发的可达性分析
- 3.5.7 Garbage First收集器