G1垃圾回收器简介及回收过程

一.什么是G1
同CMS一样,G1也是关注停顿时间,不过它是可控的,它被设计用来取代CMS,因为它是空间整理所以没有CMS那么严重的空间碎片问题,同时提供可控的停顿时间。

特性:
1.G1不同于之前的那些垃圾收集器分为连续的年轻代,老年代和永久代,而是分区(region),它将堆分为大大小小的区域(通常约为2048个),每个区域就是eden,survivor,old
2.一般优先回收包含垃圾最多的区域,所以叫Garbage-First(G1)
3.之前的垃圾收集器要么是新生代,要么是老年代,而G1兼顾年轻代和老年代
4.可控性:因为G1可以选择回收部分区域,所以可以做到停顿时间的可控性
可以看到G1的堆结构,是分为一个一个的区
在这里插入图片描述

几个名词介绍
Remembered Sets:每个区都有一个 RSet,用于记录进入该区块的对象引用(如区块 A 中的对象引用了区块 B,区块 B 的 Rset 需要记录这个信息),可以避免扫描整个区,而只需要扫描Rset就行,它用于实现收集过程的并行化以及使得区块能进行独立收集。总体上 Remembered Sets 消耗的内存小于 5%。

Collection Sets: GC中待回收的region的集合。CSet中可能存放着各个分代的Region。CSet中的存活对象会在gc中被移动(复制)。GC后CSet中的region会成为可用分区。

Card Table Java虚拟机用了一个叫做CardTable(卡表)的数据结构来**标记老年代的某一块内存区域中的对象是否持有新生代对象的引用,**卡表的数量取决于老年代的大小和每张卡对应的内存大小,每张卡在卡表中对应一个比特位,当老年代中的某个对象持有了新生代对象的引用时,JVM就把这个对象对应的Card所在的位置标记为dirty(bit位设置为1),这样在Minor GC时就不用扫描整个老年代,而是扫描Card为Dirty对应的那些内存区域。

Humongous region: 是G1中存放巨型对象的分区,巨型对象是指占用了region容量的50%以上的一个对象,如果一个H区装不下一个巨型对象,则会通过连续的若干H分区来存储。因为巨型对象的转移会影响GC效率,所以并发标记阶段发现巨型对象不再存活时,会将其直接回收。ygc也会在某些情况下对巨型对象进行回收。

TLAB(Thread Local Allocation Buffer)本地线程缓冲区: 由于对象一般分配在堆上,而堆是线程共用的,因此可能会有多个线程在堆上申请空间,而每一次的对象分配都必须线程同步,会使分配的效率下降。所以在线程初始化时,同时也会在eden区申请一块指定大小的内存,只给当前线程使用,这样每个线程都单独拥有一个空间,如果需要分配内存,就在自己的空间上分配,这样就不存在竞争的情况,可以大大提升分配效率。

栈上分配: 对于那些线程私有对象(指不可能被其他线程访问的对象)可以将它们打散分配在栈上,而不是分配在堆上,这样就不需要GC了,随着线程的销毁而回收。栈空间小,对于大对象无法实现栈上分配。栈上分配依赖于逃逸分析和标量替换 。

Snapshot-At-The-Beginning(SATB): SATB是在G1 GC在并发标记阶段使用的增量式的标记算法。SATB可以理解成在GC开始之前对堆内存里的对象做一次快照,此时活的对象就认为是活的,从而形成一个对象图

二.G1的垃圾回收过程
G1的垃圾回收过程可能包含4部分:
1.新生代GC
2.并发标记周期
3.混合回收
4.Full GC

1.新生代GC:
和其他垃圾收集器差不多,回收eden区,转移survivor区或者晋升年老的。区别是会动态调整新生代大小,基于历史ygc信息和-XX:MaxGCPauseMillis设置的停顿时间。

2.并发标记周期:(和CMS有点类似)主要是标记可回收对象,回收掉完全空闲的区域,过程:

  • 初始标记:标记从根节点可直接到达对象,会伴随一次ygc,STW
  • 根区域扫描:因为已经进行了ygc,所以只有survivor区有对象,扫描survivor区到老年代的引用(不能有ygc,必须等结束才行, 因为ygc会导致survivor区变化)
  • 并发标记:扫描并查找整个堆的存活对象,做好标记(可ygc,会中断标记过程)
  • 重新标记:STW,使用SATB,会在标记之初为存活对象创建一个快照,加快重新标记的速度
  • 独占清理:计算各个区域存活对象和回收比例并排序,识别可供混合回收的区域,更新RemeberedSet,该阶段给需要混合回收的区域做了标记,STW
  • 并发清理:识别并清理完全空闲的区域

3.混合回收
并发标记周期,虽然有部分对象回收,但是总体上说,回收的比例是比较低的。
混合回收不仅进行ygc,而且会回收之前标记出来垃圾最多的区域

4.Full GC
在混合回收过程中,当内存不足时也会触发Full GC
①.concurrent mode failure
②.大对象分配失败
③.晋升失败

三.G1 GC实例

参数设置
-Xms10M -Xmx10M -Xmn3m -XX:+PrintGCDetails -XX:+UseG1GC
 -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
import java.util.ArrayList;

public class TestCMSGC {
    private static byte[] mem = new byte[1024 * 1024 *3];
    public static void main(String[] args) {
        ArrayList<byte[]> arrayList = new ArrayList<>();
           for(;;)
           arrayList.add(mem);
    }
}

ygc日志:

[GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0017438 secs]
   [Parallel Time: 1.5 ms, GC Workers: 6]
      [GC Worker Start (ms): Min: 104.7, Avg: 104.9, Max: 105.2, Diff: 0.4]
      [Ext Root Scanning (ms): Min: 0.4, Avg: 0.5, Max: 1.0, Diff: 0.6, Sum: 3.1]
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.0, Avg: 0.7, Max: 0.9, Diff: 0.9, Sum: 4.2]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.2]
         [Termination Attempts: Min: 1, Avg: 45.2, Max: 101, Diff: 100, Sum: 271]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
      [GC Worker Total (ms): Min: 1.0, Avg: 1.3, Max: 1.4, Diff: 0.4, Sum: 7.6]
      [GC Worker End (ms): Min: 106.2, Avg: 106.2, Max: 106.2, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.0 ms]
   [Other: 0.2 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.1 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.0 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 3072.0K(3072.0K)->0.0B(2048.0K) Survivors: 0.0B->1024.0K Heap: 6000.1K(10.0M)->4152.0K(10.0M)]
Parallel Time: 1.5 ms 所有GC线程的花费时间,1.6毫秒
GC Worker Start  表示这里6个GC线程启动时间
Ext Root Scanning  根扫描时间
Update RS 更新记忆集,Remembered Sets:每个区块都有一个 RSet,用于记录进入该区块的对象引用(如区块 A 中的对象引用了区块 B,区块 B 的 Rset 需要记录这个信息),可以避免扫描整个区,而只需要扫描Rset就行,它用于实现收集过程的并行化以及使得区块能进行独立收集。
Scan RS 扫描RS时间
Object Copy  在正式回收前,G1会将存活对象放在其他区域,因此需要对象复制
Termination 线程花在终止阶段的耗时,GC线程终止前,会检查还有没对象没处理完,如果没处理完,请求终止的GC线程会去帮助完成。Termination Attempts 表示每个工作线程尝试终止的次数
GC Worker Other  GC线程哈在其他任务的耗时
GC Worker Total  GC耗时
GC Worker End   单个GC线程结束的时间
Other 其他几个任务的耗时
Choose CSet  
Ref Proc  处理弱引用,软引用的时间
Ref Enq   弱引用,软引用的入队时间
Redirty Cards  重新脏化卡表
Humongous Register,Humongous Reclaim   主要是对巨型对象回收的信息,youngGC阶段会对RSet中有引用的短命的巨型对象进行回收,巨型对象会直接回收而不需要进行转移(转移代价巨大,也没必要)

Free CSet  是否被回收的Cset区域

混合标记周期日志:
可以看到在ygc完成后就开始并发标记周期

[GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0010437 secs]
[Parallel Time: 0.8 ms, GC Workers: 6]
[GC Worker Start (ms): Min: 77.5, Avg: 77.6, Max: 77.8, Diff: 0.3]
[Ext Root Scanning (ms): Min: 0.0, Avg: 0.3, Max: 0.7, Diff: 0.7, Sum: 1.9]
[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Object Copy (ms): Min: 0.0, Avg: 0.3, Max: 0.4, Diff: 0.4, Sum: 2.1]
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
[Termination Attempts: Min: 1, Avg: 2.5, Max: 5, Diff: 4, Sum: 15]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
[GC Worker Total (ms): Min: 0.5, Avg: 0.7, Max: 0.8, Diff: 0.3, Sum: 4.4]
[GC Worker End (ms): Min: 78.3, Avg: 78.3, Max: 78.3, Diff: 0.0]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.0 ms]
[Other: 0.2 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.1 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 0.0 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.0 ms]
[Eden: 2048.0K(3072.0K)->0.0B(2048.0K) Survivors: 0.0B->1024.0K Heap: 1621.2K(10.0M)->704.1K(10.0M)]
[Times: user=0.00 sys=0.00, real=0.00 secs]
可以看到在上面ygc完成后就开始并发标记周期,依次是根扫描,并发标记,重新标记,独占清理,并发清理
[GC concurrent-root-region-scan-start]
[GC concurrent-root-region-scan-end, 0.0003243 secs]
[GC concurrent-mark-start]
[GC concurrent-mark-end, 0.0000137 secs]
[GC remark [Finalize Marking, 0.0000911 secs] [GC ref-proc, 0.0000384 secs] [Unloading, 0.0005454 secs], 0.0007483 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
[GC cleanup 6904K->6904K(10M), 0.0001752 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
[GC concurrent-cleanup-start]
[GC concurrent-cleanup-end, 0.0002721]

更多G1 GC日志可以参考这个:
https://www.cnblogs.com/javaadu/p/11220234.html

四.参数设置

-XX:+UseG1GC

使用 G1 收集器

-XX:MaxGCPauseMillis=200
-XX:G1MixedGCCountTarget:一次全局并发标记之后,后续最多执行的MixedGC次数。 默认值是8.

指定目标停顿时间,默认值 200 毫秒。

在设置 -XX:MaxGCPauseMillis 值的时候,不要指定为平均时间,而应该指定为满足 90% 的停顿在这个时间之内。记住,停顿时间目标是我们的目标,不是每次都一定能满足的。

-XX:InitiatingHeapOccupancyPercent=45

整堆使用达到这个比例后,触发并发标记周期,默认 45%。

如果要降低晋升失败的话,通常可以调整这个数值,使得并发周期提前进行

-XX:NewRatio=n

老年代/年轻代,默认值 2,即 1/3 的年轻代,2/3 的老年代

不要设置年轻代为固定大小,否则:

G1 不再需要满足我们的停顿时间目标
不能再按需扩容或缩容年轻代大小
-XX:SurvivorRatio=n

Eden/Survivor,默认值 8,这个和其他分代收集器是一样的

-XX:MaxTenuringThreshold =n

从年轻代晋升到老年代的年龄阈值,也是和其他分代收集器一样的

-XX:ParallelGCThreads=n

并行收集时候的垃圾收集线程数

-XX:ConcGCThreads=n

并发标记阶段的垃圾收集线程数

增加这个值可以让并发标记更快完成,如果没有指定这个值,JVM 会通过以下公式计算得到:

ConcGCThreads=(ParallelGCThreads + 2) / 4^3

-XX:G1ReservePercent=n

堆内存的预留空间百分比,默认 10,用于降低晋升失败的风险,即默认地会将 10% 的堆内存预留下来。

-XX:G1HeapRegionSize=n

每一个 region 的大小,默认值为根据堆大小计算出来,取值 1MB~32MB,这个我们通常指定整堆大小就好了。
-XX:G1MixedGCCountTarget
一次全局并发标记之后,后续最多执行的MixedGC次数。 默认值是8.

参考:
https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html
https://javadoop.com/post/g1
https://www.jianshu.com/p/9edcbc4bcb8b?from=singlemessage
https://blog.csdn.net/lijingyao8206/article/details/80566384
https://blog.csdn.net/lijingyao8206/article/details/80513383
https://ezlippi.com/blog/2018/01/jvm-card-table-turning.html

猜你喜欢

转载自blog.csdn.net/u010857795/article/details/112972045