G1回收器详解

从CMS垃圾回收器缺点说起

1)过于占用CPU资源,牺牲吞吐量  -- 所有回收器的通病
2)并发清理阶段存在浮动垃圾;   -- 并发执行导致
3)fgc算法是标记清除,会产生磁盘碎片 -- 标记整理算法导致
4)新生代配合ParNewGC使用,存在STW问题。 --- 时间不可控,如果heap很大,可能GC时间很大,影响线上服务

G1起源

一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存(大概6G以上)的机器;
G1从jdk7开始,jdk9被设为默认垃圾收集器;目标就是彻底替换掉CMS

G1内存分配策略

将内存分成一个一个的region,且不要求各部分是连续的。
每个Region的大小在JVM启动时就确定,JVM通常生成2000个左右的heap区, 根据堆内存的总大小,区的size范围为1-32Mb,一般4M.

region类型

三种常见: Eden, Survivor, 或 old generation(老年代)区
巨无霸区:保存比标准region区大50%及以上的对象,存储在一组连续的区中.转移会影响GC效率,标记阶段发现巨型对象不再存活时,会被直接回收。
未使用区:未被使用的region
特别说明:某个region的类型不是固定的,比如一次ygc过后,原来的Eden的分区就会变成空闲的可用分区,随后也可能被用作分配巨型对象

G1中重要的数据结构和算法

1 本地线程缓冲区,TLAB

Thread Local Allocation Buffer,默认启用,分配在Eden空间,属于单个线程,,每一个线程都有一个TLAB用于分配对象,避免了同步机制使对象尽快的分配出来。

2 晋升本地分配缓冲区,PLAB

Promotion Local Allocation Buffer,晋升的过程,无论是晋升到S还是OLd区,都是在GC线程的PLAB中进行。每个GC线程都有一个PLAB。

3 待收集集合,CSets

Collection Sets,GC中待回收的region的集合。CSet中可能存放着各个分代的Region。CSet中的存活对象会在gc中被移动(复制)。GC后CSet中的region会成为可用分区。
策略:由G1MixedGCLiveThresholdPercent参数控制的,old代分区中的存活对象比,降低到该阀值时,这个old分区会被放入CSet,后面会被执行回收整理。

4 卡表,Card Table

Java堆划分为相等大小的一个个区域,这个小的区域(一般size在128-512字节)被当做Card,而Card Table维护着所有的Card。Card Table的结构是一个字节数组,Card Table用单字节的信息映射着一个Card。当Card中存储了对象时,称为这个Card被脏化了(dirty card)。 对于一些热点Card会存放到Hot card cache。同Card Table一样,Hot card cache也是全局的结构。

5 已记忆集合,RSets

RememberedSets,存储着其他分区中的对象对本分区对象的引用,每个分区有且只有一个RSet。用于提高GC效率。
YGC时,GC root主要是两类:栈空间和老年代分区到新生代分区的引用关系。所以记录老年代分区对新生代分区的引用
Mixed GC时,由于仅回收部分老年代分区,老年代分区之间的引用关系也将被使用。所以记录老年代分区之间的引用
因此,我们仅需要记录两种引用关系:老年代分区引用新生代分区,老年代分区之间的引用。
因为每次GC都会扫描所有young区对象,所以RSet只有在扫描old引用young,old引用old时会被使用。
详细可参考: https://blog.csdn.net/a860MHz/article/details/97276211

6 Snapshot-At-The-Beginning(SATB)

SATB是在G1 GC在并发标记阶段使用的增量式的标记算法。并发标记是并发多线程的,但并发线程在同一时刻只扫描一个分区。
在解释SATB前先要了解三色标记法。三色标记法是将对象的存活状态用三种颜色标记,从黑色到灰色逐层标记:
黑:该对象被标记了,并且其引用的对象也都被标记完成。
灰:对象被标记了,但其引用的对象还没有被标记完。
白:对象还没有被标记,标记阶段结束后,会被回收。
问题场景:
有A->B->C,A是GCRoots关联的对象,那么首先会把GC Roots 标记,也就是A标记成灰色(证明现在正在搜索A相关的),然后搜索A的引用,也就是B,那么搜索了B,把B变成了灰色,那么A就搜索完成了。A 就变成了黑色,证明 A 已经 ok 了。
此时准备要搜索 B 了。
刚好,此时,用户线程要执行了,用户线程把原来 A -> B -> C 的引用改成了 A -> C,同时 B 不再引用C。
最后,C 怎么办,C还是白色的呢,白色的是不会搜索,当做垃圾处理的。
解决办法
此时的解决办法就是有一个叫做写入屏障的东西。就是说,如果A已经被标记了(已经是黑色的了),那么用户线程改动 A->C的时候,会把 C 变成灰色,这样,以后就可以搜索 C了。

几个问题:

G1的内存占用
如果从 ParallelOldGC或者CMS收集器迁移到G1, 会看到JVM进程占用更多的内存,云因就是上面的RSets、CSets、cardTable等。
但是这些加快了gc的速度,减少了stw的时间。

CSet是怎么维护的?怎么知道哪些是要回收的?
由G1MixedGCLiveThresholdPercent参数控制的,old代分区中的存活对象比,达到阀值时,说明该region可以被回收的对象比较多,这个old分区会被放入CSet,等待被GC。

cardTable和region是什么关系?对region的细分吗?有什么用?
可以认为region切分为一个一个固定大小card。而CardTable是一个全局的存储结构,其通过一个byte数组结构存储了对于每个Card的Entry,其不需要太大的存储空间。而RSet中的HashTable也就是一些其他Region(引用了RSet所在的Region)的card集合。
参考:https://blog.csdn.net/lijingyao8206/article/details/80513383

cardTable和RSets
RSets:哈希表来存储,key是region index,value是card数组
cardTable:记录多个cardPage信息,单个CardPage大小为512字节,卡表(Card Table)被实现为一个简单的字节数组,即卡表的每个标记项为1个字节。当对一个对象引用进行写操作时(对象引用改变),写屏障逻辑将会标记对象所在的卡页为dirty。如:
CARD_TABLE [this address >> 9] = 0;

G1垃圾回收方式

整体回收算法

收集整体是使用“标记-整理”,Region之间基于“复制”算法,GC后会将存活对象复制到可用分区(未分配的分区),所以不会产生空间碎片。

G1的GC类型

1)Ygc:仅处理年轻代region
2)MixedGc:包含所有年轻代以及部分老年代Region。
3)FullGc:全堆扫描,每个Region

GC原则

G1Gc会在无法分配对象或者巨型对象无法获得连续分区来分配空间时,优先尝试扩展堆空间来获得更多的可用分区。
原则上G1会计算执行GC的时间,并且极力减少花在GC上的时间(包括ygc,mixgc),如果可能,会通过不断扩展堆空间来满足对象分配、转移的需要。

youngGC

触发:分配一般对象(非巨型对象)时,当所有eden的region使用达到最大阀值并且无法申请足够内存时。
younggc会回收所有Eden以及Survivor区,并且将存活对象复制到Old区以及另一部分的Survivor区。到Old区的标准就是在PLAB中得到的计算结果。因为YoungGC会进行根扫描,所以会stop the world。
YoungGC的回收过程如下:
根扫描,跟CMS类似,Stop the world,扫描GC Roots对象。
处理Dirty card(即保存了对象的小区域), 更新RSet(分区间对象的引用关系)。
扫描RSet,扫描old区对象对于本young区的引用。
拷贝扫描出的存活的对象到survivor2或old区
处理引用队列,软引用,弱引用,虚引用

MixGC

触发:一次YoungGc之后,老年代占据堆内存的百占比超过InitiatingHeapOccupancyPercent(默认45%)时,超过这个值就会触发mixedGC。
混合回收都是基于复制算法进行的,把要回收的Region区存活的对象放入其他Region,然后这个Region全部清理掉,这样就会不断空出来新的Region;
有一个参数-XX:G1HeapWastePercent,默认值5%,即空出来的区域大于整个堆的5%,就会立即停止混合回收了。如正常默认回收次数是8次,但是可能到了4次,空闲Region大于整个堆的5%,就不会再进行后续回收了。

youngGC后,直接复用YoungGC的全局的根扫描结果(可提高效率),只回收部分老年代的Region
MixGc过程:
1)标记GCroots,一般直接复用YoungGC中的结果
2)根分区扫描(RootRegionScan)。这个阶段GC的线程可以和应用线程并发运行。其主要扫描初始标记以及之前YoungGC对象转移到的Survivor分区,并标记Survivor区中引用的对象。所以此阶段的Survivor分区也叫根分区(RootRegion)
3)并发标记(ConcurrentMark)。会并发标记所有非完全空闲的分区的存活对象,也即使用了SATB算法,标记各个分区。
4)最终标记(Remark)。主要处理SATB缓冲区,以及并发标记阶段未标记到的漏网之鱼(存活对象),会STW,可以参考上文的SATB处理。
5)清除阶段(Clean UP)。整理堆分区,调整相应的RSet(比如如果其中记录的Card中的对象都被回收,则这个卡片的也会从RSet中移除),如果识别到了完全空的分区,则会清理这个分区的RSet。这个过程会STW。
6)对存活对象进行转移(复制算法),转移到其他可用分区,所以当前的分区就变成了新的可用分区。复制转移主要是为了解决分区内的碎片问题。

FullGc

G1在对象复制/转移失败或者没法分配足够内存(比如巨型对象没有足够的连续分区分配)时,会触发FullGC。
开始版本FullGC使用的是stop the world的单线程的Serial Old模式。
JDK10以后,Full GC已经是并行运行,在很多场景下,其表现还略优于 Parallel GC 的并行 Full GC 实现。
但是仍然要避免fgc。

G1适用场景

G1的首要目标是为需要大量内存的系统提供一个保证GC低延迟的解决方案. 也就是说堆内存最低在6GB及以上,稳定和可预测的暂停时间小于0.5秒.
如果应用程序具有如下的一个或多个特征,那么将垃圾收集器从CMS或ParallelOldGC切换到G1将会大大提升性能.
1)Full GC 次数太频繁或者消耗时间太长.
2)受够了太长的垃圾回收或内存整理时间(0.5-1s)

最佳实践指导

1 不要设置年轻代的大小

通过 -Xmn 显式地指定了年轻代的大小, 会干扰到 G1收集器的默认行为.
1)G1在垃圾收集时将不再关心暂停时间指标. 所以从本质上说,设置年轻代的大小将禁用暂停时间目标.
2)G1在必要时也不能够增加或者缩小年轻代的空间. 因为大小是固定的,所以对更改大小无能为力.

G1收集器在运行的时候会调整新生代和老年代的大小。通过改变代的大小来调整对象晋升的速度以及晋升年龄,从而达到我们为收集器设置的暂停时间目标。设置了新生代大小相当于放弃了G1为我们做的自动调优。我们需要做的只是设置整个堆内存的大小,剩下的交给G1自己去分配各个代的大小。所以一般不要设置年轻代大小。
但在某些情况下,通过设置这些值会比G1算法更准确地反映新/旧堆的正确分布.

2 设置 XX:MaxGCPauseMillis=<N>

其值不应该使用平均响应时间,应该考虑使用目标时间的90%或者更小作为响应时间指标. 即90%的用户(客户端/?)请求响应时间不会超过预设的目标值;

3 转移失败

survivors 或 promoted objects 进行GC时如果JVM的heap区不足就会发生提升失败(promotion failure). 堆内存不能继续
扩充,因为已经达到最大值了. 当使用 -XX:+PrintGCDetails 时将会在GC日志中显示 to-space overflow (to-空间溢出)。该操作很昂贵,原因如下:
1)GC仍继续所以空间必须被释放. 
2)拷贝失败的对象必须被放到正确的位置(tenured in place). 
3)CSet指向区域中的任何 RSets 更新都必须重新生成(regenerated). 

避免转移失败的方法:
1)增加保留内存大小, 其默认值是 10;G1保留内存大小,非必须不会使用保留内存;即增大-XX:G1ReservePercent=n
2)更早启动标记周期(marking cycle).即InitiatingHeapOccupancyPercent设置的小一点??
3)增加标记线程(marking threads)的数量. 合理设置-XX:ConcGCThreads=n

4 新生代优化-避免短生命对象进入老年代

预估每次Minor GC后存活下来对象的大小,合理的设置Survivor区,同时考虑高峰期间时,动态年龄判断条件的影响,不要让这种短生命周期对象侥幸逃脱进入老年代

5 老年代

系统的停顿时间时关键!是核心,要预测停顿时间,并不是越小越好,过小则回收效果不大
即-XX:MaxGCPauseMills参数优化
这个参数是核心点!如果参数设置的值很大,导致系统运行很久,新生代可能都占用了堆内存的60%了,此时才触发新生代GC,那么存活下来的对象可能就会很多,此时就会导致Survivor区域放不下那么多的对象(或是动态年龄判定规则),就会进入老年代中。
如果参数设置过小,即使GC停顿时间很短,但GC频率太大,比如说30秒触发一次新生代gc,每次就停顿30毫秒,这样也是很影响系统性能的。或者GC频率过高,也可能会导致对象很容易就进入了老年代,这样也是有问题

来源:
https://www.cnblogs.com/yuanzipeng/p/13374690.html
https://backstage.forgerock.com/knowledge/kb/article/a75965340

6 生产中配置实例

ENTRYPOINT [ "/bin/bash", "-c" , "exec java --enable-preview \
-Dproject.name=prod-api \
-Xms8g -Xmx8g -Xss64m -XX:MaxGCPauseMillis=100 -XX:G1ReservePercent=20 -XX:ConcGCThreads=2 -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=512M -Xlog:gc*:file=/root/logs/coding-api/${HOSTNAME}_gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/root/logs/coding-api/${HOSTNAME}_heapdump.hprof -Dlog4j2.formatMsgNoLookups=true -Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector -DAsyncLogger.RingBufferSize=40960 -Dlog.level.console=info -Dlog.level.com.tal=info -Dlog4j.configurationFile=/app/conf/log4j2.yml -Dserver.port=8080 -jar /app.jar"]

G1相关的参数

-XX:MaxGCPauseMillis=200 - 设置最大GC停顿时间指标,JVM会尽力实现,但不保证. 默认值为200毫秒.
-XX:InitiatingHeapOccupancyPercent=45 - 如果老年代占据了堆内存的45%的时候,此时会触发一次mixGc。值为0则表示“一直执行GC循环)'. 默认值为45。
-XX:G1MixedGCLiveThresholdPercent:默认值是85%,确定要回收的Region的时候,必须是存活对象低于85%的Region才可以回收。
-XX:G1ReservePercent=n 设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10.
-XX:ConcGCThreads=n 并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同.
-XX:+UseG1GC - 让 JVM使用G1垃圾收集器, jdk9被设为默认垃圾收集器;所以如果你的版本比较新则不再需要使用该参数
-XX:MetaspaceSize=256M 元空间,默认20M,确实有点小。
-XX:MaxMetaspaceSize=512M 最大元空间

下面参数不建议修改
-XX:G1NewSizePercent=5    设置年轻代占整个堆的最小百分比,默认值是堆的5%。需要开启-XX:UnlockExperimentalVMOptions
-XX:G1MaxNewSizePercent=60    设置年轻代占整个堆的最大百分比,默认值是堆的60%。
-XX:NewRatio=n 新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2.
-XX:SurvivorRatio=n eden/survivor 空间大小的比例(Ratio). 默认值为 8.
-XX:MaxTenuringThreshold=n 年轻代提升到年老代的最大临界值. 默认值为 15.
-XX:G1HeapRegionSize=n region大小  默认值将根据 heap size 算出最优解;1M-32M
-XX:G1MixedGCCountTarget mixed回收执行次数,默认回收次数8。
-XX:G1HeapWastePercent,默认值是5%,就是说空出来的区域大于整个堆的5%,即使未达到回收次数,也会立即停止混合回收了。
如:默认回收次数是8次,但是可能到了4次,发现空闲Region大于整个堆的5%,就不会再进行后续回收了。

可参考官网说明:https://docs.oracle.com/en/java/javase/13/docs/specs/man/java.html

查看系统默认参数配置,如G1ReservePercent:  java -XX:+PrintFlagsFinal  | grep G1ReservePercent

JVM初始分配的堆内存由-Xms指定,默认是物理内存的1/64;
JVM最大分配的堆内存由-Xmx指定,默认是物理内存的1/4。
默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。


G1的gc日志

ps -ef | grep java
其中-Xlog:gc*:file=/data/logs/gc_log/api-gc.log 说明了gc日志路径

凌晨4点启动后刚开始的日志:
 more /data/logs/gc_log/coding-api-gc.log
[0.004s][info][gc,heap] Heap region size: 4M //每个region大小
[0.033s][info][gc     ] Using G1 
[0.033s][info][gc,heap,coops] Heap address: 0x0000000500000000, size: 12288 MB, Compressed Oops mode: Zero based, Oop shift amount: 3
[0.033s][info][gc,cds       ] Mark closed archive regions in map: [0x00000007ffc00000, 0x00000007ffc79ff8]
[0.033s][info][gc,cds       ] Mark open archive regions in map: [0x00000007ffb00000, 0x00000007ffb47ff8]
[0.046s][info][gc           ] Periodic GC disabled

*************下面开始第一次gc
[1.844s][info][gc,start     ] GC(0) Pause Young (Concurrent Start) (Metadata GC Threshold) //在新生代进行垃圾回收
[1.844s][info][gc,task      ] GC(0) Using 8 workers of 8 for evacuation  //使用8条垃圾回收线程
[1.923s][info][gc,phases    ] GC(0)   Pre Evacuate Collection Set: 0.7ms
[1.923s][info][gc,phases    ] GC(0)   Evacuate Collection Set: 76.6ms
[1.923s][info][gc,phases    ] GC(0)   Post Evacuate Collection Set: 1.2ms
[1.923s][info][gc,phases    ] GC(0)   Other: 1.1ms
[1.923s][info][gc,heap      ] GC(0) Eden regions: 115->0(139)
[1.923s][info][gc,heap      ] GC(0) Survivor regions: 0->14(20)
[1.923s][info][gc,heap      ] GC(0) Old regions: 0->0
[1.923s][info][gc,heap      ] GC(0) Archive regions: 2->2
[1.923s][info][gc,heap      ] GC(0) Humongous regions: 0->0
[1.923s][info][gc,metaspace ] GC(0) Metaspace: 20586K->20586K(1067008K)
[1.923s][info][gc           ] GC(0) Pause Young (Concurrent Start) (Metadata GC Threshold) 463M->56M(12288M) 79.600ms
[1.923s][info][gc,cpu       ] GC(0) User=0.10s Sys=0.03s Real=0.08s

[1.923s][info][gc           ] GC(1) Concurrent Cycle  //并发标记周期     
[1.923s][info][gc,marking   ] GC(1) Concurrent Clear Claimed Marks   //初始标记
[1.923s][info][gc,marking   ] GC(1) Concurrent Clear Claimed Marks 0.027ms
[1.923s][info][gc,marking   ] GC(1) Concurrent Scan Root Regions   //扫描GCroot所在的region
[1.938s][info][gc,marking   ] GC(1) Concurrent Scan Root Regions 15.193ms
[1.938s][info][gc,marking   ] GC(1) Concurrent Mark (1.938s)  //并发标记
[1.938s][info][gc,marking   ] GC(1) Concurrent Mark From Roots
[1.938s][info][gc,task      ] GC(1) Using 2 workers of 2 for marking
[1.942s][info][gc,marking   ] GC(1) Concurrent Mark From Roots 3.189ms
[1.942s][info][gc,marking   ] GC(1) Concurrent Preclean      //并发预清理
[1.942s][info][gc,marking   ] GC(1) Concurrent Preclean 0.123ms
[1.942s][info][gc,marking   ] GC(1) Concurrent Mark (1.938s, 1.942s) 3.345ms
[1.942s][info][gc,start     ] GC(1) Pause Remark   //暂停标记
[1.943s][info][gc           ] GC(1) Pause Remark 62M->62M(12288M) 1.449ms
[1.943s][info][gc,cpu       ] GC(1) User=0.01s Sys=0.00s Real=0.00s
[1.943s][info][gc,marking   ] GC(1) Concurrent Rebuild Remembered Sets  //重新构建remembered sets
[1.944s][info][gc,marking   ] GC(1) Concurrent Rebuild Remembered Sets 0.816ms
[1.944s][info][gc,start     ] GC(1) Pause Cleanup
[1.945s][info][gc           ] GC(1) Pause Cleanup 62M->62M(12288M) 0.377ms
[1.945s][info][gc,cpu       ] GC(1) User=0.00s Sys=0.00s Real=0.00s
[1.945s][info][gc,marking   ] GC(1) Concurrent Cleanup for Next Mark
[2.021s][info][gc,marking   ] GC(1) Concurrent Cleanup for Next Mark 75.935ms
[2.021s][info][gc           ] GC(1) Concurrent Cycle 97.722ms


[3.762s][info][gc,start     ] GC(2) Pause Young (Concurrent Start) (Metadata GC Threshold)
[3.762s][info][gc,task      ] GC(2) Using 8 workers of 8 for evacuation
[3.813s][info][gc,phases    ] GC(2)   Pre Evacuate Collection Set: 0.7ms
[3.813s][info][gc,phases    ] GC(2)   Evacuate Collection Set: 48.3ms
[3.813s][info][gc,phases    ] GC(2)   Post Evacuate Collection Set: 1.4ms
[3.813s][info][gc,phases    ] GC(2)   Other: 0.6ms
[3.813s][info][gc,heap      ] GC(2) Eden regions: 121->0(149)
[3.813s][info][gc,heap      ] GC(2) Survivor regions: 14->4(20)
[3.813s][info][gc,heap      ] GC(2) Old regions: 0->14
[3.813s][info][gc,heap      ] GC(2) Archive regions: 2->2
[3.813s][info][gc,heap      ] GC(2) Humongous regions: 0->0
[3.813s][info][gc,metaspace ] GC(2) Metaspace: 35026K->35026K(1081344K)
[3.813s][info][gc           ] GC(2) Pause Young (Concurrent Start) (Metadata GC Threshold) 538M->71M(12288M) 51.174ms
[3.813s][info][gc,cpu       ] GC(2) User=0.25s Sys=0.05s Real=0.05s

11小时后下午3点左右的日志:
[38918.626s][info][gc,start     ] GC(101) Pause Young (Normal) (G1 Evacuation Pause)
[38918.626s][info][gc,task      ] GC(101) Using 8 workers of 8 for evacuation
[38918.639s][info][gc,phases    ] GC(101)   Pre Evacuate Collection Set: 0.6ms
[38918.639s][info][gc,phases    ] GC(101)   Evacuate Collection Set: 10.4ms
[38918.639s][info][gc,phases    ] GC(101)   Post Evacuate Collection Set: 1.8ms
[38918.639s][info][gc,phases    ] GC(101)   Other: 0.6ms
[38918.639s][info][gc,heap      ] GC(101) Eden regions: 1840->0(1840)
[38918.639s][info][gc,heap      ] GC(101) Survivor regions: 3->3(231)
[38918.639s][info][gc,heap      ] GC(101) Old regions: 41->41
[38918.639s][info][gc,heap      ] GC(101) Archive regions: 2->2
[38918.639s][info][gc,heap      ] GC(101) Humongous regions: 2->2
[38918.639s][info][gc,metaspace ] GC(101) Metaspace: 156518K->156518K(1193984K)
[38918.639s][info][gc           ] GC(101) Pause Young (Normal) (G1 Evacuation Pause) 7547M->186M(12288M) 13.485ms
[38918.639s][info][gc,cpu       ] GC(101) User=0.09s Sys=0.00s Real=0.02s

[39117.315s][info][gc,start     ] GC(102) Pause Young (Normal) (G1 Evacuation Pause)
[39117.315s][info][gc,task      ] GC(102) Using 8 workers of 8 for evacuation
[39117.328s][info][gc,phases    ] GC(102)   Pre Evacuate Collection Set: 0.6ms
[39117.328s][info][gc,phases    ] GC(102)   Evacuate Collection Set: 10.1ms
[39117.328s][info][gc,phases    ] GC(102)   Post Evacuate Collection Set: 1.9ms
[39117.328s][info][gc,phases    ] GC(102)   Other: 0.6ms
[39117.328s][info][gc,heap      ] GC(102) Eden regions: 1840->0(1840)
[39117.328s][info][gc,heap      ] GC(102) Survivor regions: 3->3(231)
[39117.328s][info][gc,heap      ] GC(102) Old regions: 41->41
[39117.328s][info][gc,heap      ] GC(102) Archive regions: 2->2
[39117.328s][info][gc,heap      ] GC(102) Humongous regions: 2->2
[39117.328s][info][gc,metaspace ] GC(102) Metaspace: 156519K->156519K(1193984K)
[39117.328s][info][gc           ] GC(102) Pause Young (Normal) (G1 Evacuation Pause) 7546M->186M(12288M) 13.206ms
[39117.328s][info][gc,cpu       ] GC(102) User=0.09s Sys=0.00s Real=0.01s

userTime是指gc花费的所有CPU时间之和,realTime是指gc暂停的实际时间。


下面三个都是什么含义? 
[38918.626s][info][gc,start     ] GC(101) Pause Young (Normal) (G1 Evacuation Pause) 新生代--发生于后期
[3.762s][info][gc,start     ] GC(2) Pause Young (Concurrent Start) (Metadata GC Threshold)  -元空间扩容- 项目启动
[1.923s][info][gc           ] GC(1) Concurrent Cycle  //并发标记周期     项目启动
GC pause (G1 Evacuation Pause) (mixed) 
是不是没有发生过mixedGc,需要一个机器不重启查看,至今还没弄明白


元数据扩容:
1)新生代并发回收  Pause Young (Concurrent Start) (Metadata GC Threshold)
2)并发周期标记   Concurrent Cycle
3)(Concurrent Start) (Metadata GC Threshold)
4) Concurrent Cycle
5) Pause Young (Concurrent Start) (Metadata GC Threshold)
6) Concurrent Cycle
7) Pause Young (Concurrent Start) (Metadata GC Threshold)
8) Concurrent Cycle

gc次数统计
jstat -gc 22332
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC       MU       CCSC    CCSU      YGC     YGCT    FGC    FGCT    CGC    CGCT     GCT
 0.0  12288.0  0.0   11505.7 7917568.0 4788224.0 4653056.0   196096.5 163632.0 156534.1 19580.0 16987.9     98    1.760   0      0.000   10      0.021    1.781


jstat -gc结果中CCSC和CCSU、CGC、CGCT分别是什么?
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
CGC:Concurrent gc,metaspace扩容的时候就发生了
CGCT:Concurrent gc time

G1的优缺点

缺点

region 大小和大对象很难保证一致,这会导致空间的浪费;特别大的对象是可能占用超过一个 region 的。并且,region 太小不合适,会令你在分配大对象时更难找到连续空间,这是一个长久存在的情况。
因为CSet、Rsets、CardTable等数据的存在,会比较耗费内存。

优点
1、可根据用户设置停顿时间,制定回收计划(但是也可能存在超出用户的停顿时间).  --- 最主要的目标
2、局部上来看是基于“复制”算法实现的,无磁盘碎片。
3、对GC停顿可以做更好的预测

总结

G1最大的特性/优点,就是对停顿时间的处理,一方面通过统计数据结构(会耗费更多内存)、参数配置实现控制和预测停顿时间,另一方面对于大内存的回收采取分批回收的方式,降低单次GC的停顿时间,避免影响主业务。

G1怎么实现的回收时间可控
1)追踪每一个Region的回收价值,所谓回收价值就是根据设定的预期系统停顿时间,来选择最少回收时间和最多回收对象的Region进行垃圾回收,保证每次回收都是最有效的回收
2)如果需要被Gc的对象很多,保证GC对系统停顿的影响在可控范围内,采取分批循环回收的策略,尽量保证单次服务停顿满足要求。
核心思想:贪心算法 + 分批回收

G1和CMS适用场景
实际用cms的挺多,也有更多经验;
如果都不熟,先看看g1能否达到你的延迟、吞吐目标;
还有基础配置,如堆大小,比较大,比如16g以上,建议优先g1;30G以上慎用CMS
如果gc是stw时间过长,一般也是通过G1来解决

最新的回收器

Epsilon GC,简单说就是个不做垃圾收集的 GC,似乎有点奇怪,有的情况下,例如在进行性能测试的时候,可能需要明确判断 GC 本身产生了多大的开销,这就是其典型应用场景。
ZGC,Oracle开源出来的一个超级GC实现,具备令人惊讶的扩展能力,比如支持Tbytes 级别的堆大小,并且保证绝大部分情况下,延迟都不会超过10ms。虽然目前还处于实验阶段,仅支持 Linux 64 位的平台,但其已经表现出的能力和潜力都非常令人期待。可参考:https://mp.weixin.qq.com/s/ag5u2EPObx7bZr7hkcrOTg


gc日志参考url
https://blog.csdn.net/weixin_43931625/article/details/105031002
https://www.cnblogs.com/javaadu/p/11220234.html
https://www.cnblogs.com/javaadu/p/11742585.html  

优化参考:
https://blog.csdn.net/weixin_45410925/article/details/102386767
-XX:+UseStringDeduplication
https://blog.csdn.net/weishuai528/article/details/96899513
-Xms128M -Xmx2048M
可参考url:
https://blog.csdn.net/lijingyao8206/article/details/80513383 ---好文章
https://www.cnblogs.com/ASPNET2008/p/6496481.html  
https://blog.csdn.net/qq_27529917/article/details/86664677 --参数
https://blog.csdn.net/qq_43294955/article/details/105565152 --优质, G1垃圾回收器详解与回收性能优化
https://docs.oracle.com/cd/E40972_01/doc.70/e40973/cnf_jvmgc.htm#autoId0

  • 6
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
G1(Garbage First)垃圾回收是一种低延迟的垃圾回收,它可以在不影响应用程序吞吐量的情况下,有效地处理大量的内存垃圾。下面是G1垃圾回收的执行流程: 1. 初始标记(Initial Mark):该阶段的目标是标记所有的根对象,并且标记从根对象直接可达的对象。为了达到这个目的,G1垃圾回收会扫描所有的Java线程的栈,以及记录下所有的GC Root。 2. 并发标记(Concurrent Mark):在初始标记之后,G1垃圾回收会开始并发的标记所有从根对象可达的对象。这是一个并发的过程,不会阻塞应用程序的执行。 3. 最终标记(Final Mark):在并发标记之后,G1垃圾回收会再次暂停应用程序的执行,以完成所有未被标记的存活对象的标记。这个过程与初始标记是类似的。 4. 筛选回收(Live Data Counting and Evacuation):在最终标记之后,G1垃圾回收会计算每个区域中存活的数据量。然后,它会选定一些区域作为回收集(Collection Set),将这些区域中的存活对象复制到空闲的区域中,并将这些区域标记为可回收的。 5. 清除(Cleanup):在筛选回收之后,G1垃圾回收会开始清理所有被标记为可回收的区域。 需要注意的是,G1垃圾回收是一个全局垃圾回收,因此它不仅仅会处理单个堆区域的垃圾回收,而是会处理整个Java堆。同时,它还会根据应用程序运行的情况,动态地调整回收集的大小,以达到最佳的垃圾回收效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值