前言
最近在工作中遇到频繁FullGC且YoungGC时间有时特别长的情况,而自己对JVM的垃圾回收也是一知半解,因此需要对JVM做系统的了解,为快速解决工作中的问题,能有效分析GC日志和业务代码,先从G1垃圾回收器开始学习(工作中系统采用的就是G1垃圾回收器)
本文概览
准备阶段
什么是GC
GC全称Garbage Collection,意为垃圾收集。在系统不停机运行中,应用会不断创建对象,也就是不断的在内存中进行空间分配。但系统内存是一定的,不可能支持无限制的内存分配,因此会针对应用持续运行中不再使用的对象所占用的内存空间进行回收。
GC算法
常见的垃圾收集算法有以下几种:
- 标记-复制
- 标记-清除
- 标记-整理
标记-复制
标记出存活对象后,将存活的对象复制到一块准备好的空闲内存区域,然后将垃圾回收的区域全部回收掉。
上图以年轻代的回收场景来举例,可以看到,复制算法有两个比较重要的点:对象复制的过程、空间区域的冗余。如果一个区域中存活对象较多的场景不适合采用复制算法来进行垃圾回收。还有就是需要准备额外的空闲区域来接收存活对象的放置,因此该算法也有额外空间的损失。
标记-清除
标记出存活对象后,将死亡的对象直接被回收掉,存活对象不做处理。
上图我们也可以看到,这种处理方式很容易造成内存碎片的问题,例如如图所示,有16M的内存空间,通过标记-清除的回收算法,回收6M的内存空间,但无法分配一个连续的4M大小的对象。
标记-整理
标记出存活对象后,将死亡的对象直接回收掉并将存活对象做一步压缩整理,将其压缩到头部连续的空间内。
这就解决了上面说的内存碎片问题。
GC回收器
Java虚拟机发展至今,共有一下几款垃圾收集器:
- 新生代
- Serial:基于标记复制算法,使用一个收集线程去完成垃圾收集工作,并且在垃圾收集过程中必须暂停其他所有工作线程,直到收集工作结束
- Parallel Scavenge:基于标记复制算法,使用多个线程去并行收集
- ParaNew:基于标记复制算法,使用多个线程去并行收集,一般与CMS组成JVM分代回收的垃圾回收器
- 老年代
- Serial Old:基于标记整理算法,使用一个收集线程去完成垃圾收集工作,通常用于客户端模式下。在服务端模式下,通常作为CMS收集器发生失败时的后备预案。
- Parallel Old:基于标记整理算法,使用多个线程去并行收集,垃圾回收的过程还是STW
- CMS:基于标记清除算法,第一个真正意义上的并发垃圾回收器
- 不分代
- G1:基于CSet进行垃圾回收,这里不赘述,下面做详细解释
- ZGC:它是一个可扩展的低延迟垃圾回收器,旨在处理多达几TB的堆内存,同时保持暂停时间在10毫秒以内,无论堆的大小如何。它使用了一种叫做颜色指针的技术来实现并发的对象整理,并极大地减少了STW暂停
- Shanendoah:它是一个低延迟的垃圾回收器,设计目标是减少GC的暂停时间,而不仅仅关注整体的吞吐量。它通过并发地压缩堆和并发地进行标记-清除,达到了最小化STW暂停的效果,使其成为响应时间要求严格的应用的一个合适选择
G1出现的背景
Garbage First(简称G1)收集器是垃圾收集器技术发展历史上的里程碑式的结果,它开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。摘自《深入理解Java虚拟机》
它主要应用于服务端的垃圾回收场景,提供软实时、低延时、可设定目标,适用于较大的堆内存(对比CMS,《深入理解Java虚拟机》作者是这么说的:在小内存应用上CMS的表现大概率仍然要会优于G1,而在大内存应用上G1则大多能发挥其优势,这个优劣势的Java堆容量平衡点通常在6GB至8GB之间)。
它的出现也是为了替换JDK 5中的CMS垃圾回收器。在JDK 9发布时将JDK默认的Parallel Scavenge+Parallel Old组合的垃圾回收器修改为G1收集器,而CMS则是被定义为不推荐使用的垃圾回收器。
G1的内存布局
分区与分代
G1将Java堆分成若干个大小一致的区域,并且Region的大小可以通过-XX:G1HeapRegionSize=N参数设定,取值范围为1MB ~ 32MB,且应为2的N次幂。下图是G1的内存分布示意图(摘自于引用文章)。
从示意图来看,G1仍然保留年轻代、老年代的概念,但新生代和老年代不再是固定的了,也就是同一块Region有可能是年轻代也有可能会被当做是老年代,并且他们在物理上也不是连续的了。图中还有Humongous区域,这个是用来存储大对象的区域,G1认为只要超过了Region大小的一半就是大对象,会被直接存储到该区域当中,它也属于老年代的一部分。
由于G1将垃圾回收的最小单位定位Region,这样就可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1收集器回去跟踪各个Region里面的垃圾堆积的"价值"(回收所获得的空间大小以及回收所需时间的经验值)大小,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间(使用参数 -XX:MaxGCPauseMillis
指定,默认值200)来优先处理回收价值收益最大的那些Region,这也是"Garbage First"名称的由来。
前面说了,G1会在逻辑上划分年轻代、老年代,其中年轻代又分为Eden、Survivor空间,但年轻代并不是固定不变的,当年轻代分区占满时,JVM会分配新的内存空间加入到年轻代中,整个年轻代内存会在初始空间-XX:G1NewSizePercent
(默认整堆5%)与最大空间(默认60%)之间动态变化,且由参数目标暂停时间-XX:MaxGCPauseMillis
(默认200ms)、需要扩缩容的大小以-XX:G1MaxNewSizePercent
及分区的已记忆集合(RSet)计算得到。当然,G1依然可以设置固定的年轻代大小(参数-XX:NewRatio、-Xmn),但同时暂停目标将失去意义。
收集集合
既然空间上不连续,那G1是如何回收的呢?总不至于去全堆扫描吧?肯定不是的😄。这就引入了Collection Set(简称CSet)来表示每次GC暂停时回收的一系列目标Region区域。
在G1中,共有两类CSet,一类为保存年轻代Region的集合,另一类为保存年轻代+老年代的Region的集合。年轻代收集集合则保存年轻代的分区,在GC时会将这些分区中的存活对象移动到空闲分区中,然后将原始分区空间全部回收掉。与年轻代集合不同是,老年代集合会有准入条件,可以通过活跃度阈值-XX:G1MixedGCLiveThresholdPercent(默认85%)进行设置,从而拦截那些回收开销巨大的对象;同时,每次混合收集可以包含候选老年代分区,可根据CSet对堆的总大小占比-XX:G1OldCSetRegionThresholdPercent
(默认10%)设置数量上限来避免长时间的暂停。
其实,G1的收集都是根据CSet进行操作的,年轻代收集与混合收集没有明显的不同,最大的区别在于两种收集的触发条件。
记忆集与卡表
我们现在知道G1是通过CSet进行垃圾回收的,那CSet是如何被构建呢?其实针对年轻代Region来讲,一般会将所有分区(有对象分配的)加入到年轻代的CSet中,而对于老年代就比较复杂了,如果从GC Roots开始一直到叶子对象这一整条引用链都分布在一个Region当中就比较方便,在扫描当前Region时就可以将这条引用链标记出来,那么当跨Region引用时,G1是如何标记的呢?肯定不是全堆扫描🌚,答案就是G1会维护一个全局卡表,他本质上是一个哈希结构,Key为每个Region的起始地址,Value是一个集合,里面存储的元素是卡表的索引号。每个Region都会维护一个记忆集。
上图是一个关于卡表&记忆集的直观图,比较清晰直接,我在学习这块的时候十分困惑,所以也在这里记录下我的理解,如果有错误还请指正。
G1维护了一个全局卡表,这个卡表(代表整个堆)分成若干个卡,也就是分成若干个连续的物理内存区域。当应用线程修改一个对象引用时,涉及的卡便会被标记为"dirty"。而每个Region又维护一个记忆集,这个记忆集本质上是一个哈希结构,key为别的Region指向自己的指针,Value是一个集合,记录了卡表的索引号。这里解释下:比如对象A指向对象B,那么在B的记忆集中就是记录对象A的指针,并且将A所对应的卡片索引号集合(之所以是一个集合,是因为一个对象可能比较大,它占用了多个卡或者一个Region中有多个对象都引用当前Region的对象,它们设计多个卡的情况)
写屏障
讲述完记忆卡与卡表的实现,那么他们的数据是如何被更新维护的呢?实际上JVM会在编译时注入一小段代码,用于记录指针变化,object.field = <reference> (putfield) ;当更新指针时,会将对应的卡标记为Dirty,将Card加入到Dirty Card Queue,这个队列会有白/绿/黄/红四个颜色,代表JVM处理队列更新记忆集的紧急程度
- 白:天下太平,无事发生。
- 绿:
-XX:G1ConcRefinementGreenZone=N
,Refinement线程开始被激活,开始更新RS - 黄:
-XX:G1ConcRefinementYellowZone=N
,全部Refinement线程开始激活 - 红:
-XX:G1ConcRefinementRedZone=N
,产生STW,应⽤线程也参与排空队列的⼯作
G1的垃圾回收原理
备注:对G1的垃圾回收原理是基于Jdk 8版本。
G1垃圾收集活动周期图
G1首先会先进行若干次young gc,每一次gc后,会将存活对象提供给PLAB(Promotion Local Allocation Buffer)来转移对象到老年代或Survivor区。当老年代堆内存占用达到阈值后(-XX:InitiatingHeapOccupancyPercent,默认45%)会进入到并发标记周期,这个周期G1会先进行一次young gc并且伴随着GC Roots的标记(栈中的对象、静态变量、常量、JNI对象等),随后进行与应用线程并发的进行存活对象标记。并发标记结束后,进行重新标记,这个时候标记一下在并发标记过程中产生对象引用更新(通过原始快照+记忆集快速实现),并发标记周期的最后一个操作就是清除阶段,该阶段是对RSet梳理、整理堆分区以及识别所有空闲分区,并发标记周期后,会产生多次mixed gc情况,并不会一次性收集完,这也是G1的启发式算法,根据用户设定的期望时间来优先回收价值最大的一批Region集合。
标记存活对象
通过三色标记法+原始快照,细节我们单拉一节来具体介绍。
Young GC
通过GC日志来看下,young gc各阶段都是干什么的
2023-09-15T16:07:33.481+0800: 158368.669: [GC pause (G1 Evacuation Pause) (young)
Desired survivor size 318767104 bytes, new threshold 15 (max 15)
(to-space exhausted), 0.5207419 secs]
[Parallel Time: 316.3 ms, GC Workers: 13]
[GC Worker Start (ms): Min: 158368670.8, Avg: 158368673.3, Max: 158368674.0, Diff: 3.2]
[Ext Root Scanning (ms): Min: 0.4, Avg: 2.5, Max: 6.1, Diff: 5.7, Sum: 32.4]
[Update RS (ms): Min: 6.5, Avg: 15.8, Max: 23.6, Diff: 17.1, Sum: 204.8]
[Processed Buffers: Min: 24, Avg: 93.5, Max: 213, Diff: 189, Sum: 1216]
[Scan RS (ms): Min: 0.0, Avg: 1.2, Max: 1.7, Diff: 1.7, Sum: 16.1]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Object Copy (ms): Min: 282.4, Avg: 291.0, Max: 300.7, Diff: 18.3, Sum: 3783.0]
[Termination (ms): Min: 0.0, Avg: 3.1, Max: 4.8, Diff: 4.8, Sum: 40.3]
[Termination Attempts: Min: 1, Avg: 1.7, Max: 5, Diff: 4, Sum: 22]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.3]
[GC Worker Total (ms): Min: 313.0, Avg: 313.6, Max: 316.1, Diff: 3.1, Sum: 4077.0]
[GC Worker End (ms): Min: 158368986.9, Avg: 158368986.9, Max: 158368987.0, Diff: 0.0]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.5 ms]
[Other: 204.0 ms]
[Evacuation Failure: 172.1 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 27.2 ms]
[Ref Enq: 0.6 ms]
[Redirty Cards: 1.2 ms]
[Humongous Register: 0.1 ms]
[Humongous Reclaim: 0.5 ms]
[Free CSet: 0.3 ms]
[Eden: 3704.0M(4808.0M)->0.0B(3024.0M) Survivors: 0.0B->0.0B Heap: 11.2G(12.0G)->7850.2M(12.0G)]
Heap after GC invocations=4016 (full 26):
garbage-first heap total 12582912K, used 8038625K [0x00000004e0800000, 0x00000004e1003000, 0x00000007e0800000)
region size 8192K, 0 young (0K), 0 survivors (0K)
Metaspace used 159472K, capacity 162234K, committed 162684K, reserved 1193984K
class space used 16726K, capacity 17320K, committed 17532K, reserved 1048576K
}
[Times: user=1.78 sys=0.04, real=0.52 secs]
young gc整个阶段是Stop The World的,日志中的并行时间其实说的是GC线程并行的工作,应用线程是被挂起的。解释完这个我们再来一块一块说明一下它的各个阶段都做了什么。
- 并行时间
- GC Worker Start:GC工作线程启动,后面的Diff指标越小越好,越小就意味着各个GC工作线程之间开始执行的时间间隔很短,或者它们几乎同时开始
- 这能说明几点:更佳的并行性、更佳的响应性、更少的启动延迟、更一致的停顿时间
- Ext Root Scanning:外部根扫描,GC工作线程并行扫描指向Collection Set的外部根(例如静态变量、Java线程、JNI引用)所耗费的时间
- Update RS:更新记忆集,这个阶段主要是处理一些脏卡(在程序运行过程中,产生的一些引用的变更,这个阶段主要是确保跨Region引用在GC过程中被正确处理)
- Processed Buffers:计数显示GC工作线程除了多少个’update buffers’
- Scan RS:扫描记忆集以查找对某个Region的引用所花费的时间,这个时间取决RSet数据结构的"粗糙程度",G1底层采用的是哈希结构(前面描述记忆集已经提到)
- Code Root Scanning:Code Cache扫描,这个阶段确保了所有直接或间接由机器代码持有的Java对象引用在年轻代GC过程中都被正确处理
- Object Copy:存活对象的移动,这个阶段是GC线程并行去处理的,它占据了停顿时间的一大部分,如果这个时间很长,需要分析下为什么会产生这么多的存活对象
- Termination:使得GC工作线程准备进入终止所花费的时间统计
- GC Worker Other:GC工作线程没有在上面列出的任何并行活动中所花费的时间统计
- GC Worker Start:GC工作线程启动,后面的Diff指标越小越好,越小就意味着各个GC工作线程之间开始执行的时间间隔很短,或者它们几乎同时开始
- 串行时间
- Code Root Fixup:更新机器码所引用的对象的地址所耗费的时间
- Code Root Purge:对一些无引用类进行卸载,删除无用代码进一步优化内存占用,这个操作所耗费的时间
- Clear CT:清除卡表所耗费的时间
- Other:其他阶段的耗时
- Evacuation Failure:分配失败担保机制,当年轻代对象进行移动时找不到连续可用的空间,JVM就会做一系列动作,其中一个动作就是可能会产生一次全堆单线程、STW式的FullGC操作,因此我们要避免出现分配失败的情况。
- Choose CSet:选择要被回收的Region集合所耗费的时间,在young gc中一般是将年轻代所有的Region加入进来
- Ref Proc:处理软引用、弱引用、虚引用花费的时间,这块的算法比较复杂,我们只需要知道这些引用的回收时机即可
- Ref Enq:将上面的引用识别出满足回收条件的并加入到对应的引用处理队列中,并进行清理所耗费的时间,
这两个阶段我学习比较混淆,没找到有效资料
,先记住这两个阶段就是保证这些引用类型能被回收阶段正确处理 - Redirty Cards:重新脏化卡片。排队引用可能会更新RSet,所以需要对关联的Card重新脏化(Redirty Cards)
- Humongous Register、Reclaim: 主要是对巨型对象回收的信息,youngGC阶段会对RSet中有引用的短命的巨型对象进行回收,巨型对象会直接回收而不需要进行转移(转移代价巨大,也没必要)
- Free CSet:释放CSet,其中也会清理CSet中的RSet
并发标记周期
并发标记周期日志如下所示:
2023-09-18T14:11:36.891+0800: 11.294: [GC pause (Metadata GC Threshold) (young) (initial-mark)
Desired survivor size 243269632 bytes, new threshold 15 (max 15)
, 0.0451481 secs]
[Parallel Time: 17.9 ms, GC Workers: 13]
[GC Worker Start (ms): Min: 11294.1, Avg: 11301.5, Max: 11302.2, Diff: 8.1]
[Ext Root Scanning (ms): Min: 0.3, Avg: 2.2, Max: 8.1, Diff: 7.8, Sum: 28.0]
[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: 1.6, Max: 6.4, Diff: 6.4, Sum: 20.7]
[Object Copy (ms): Min: 0.5, Avg: 4.7, Max: 9.0, Diff: 8.5, Sum: 60.9]
[Termination (ms): Min: 0.0, Avg: 1.9, Max: 2.3, Diff: 2.3, Sum: 24.6]
[Termination Attempts: Min: 1, Avg: 57.7, Max: 157, Diff: 156, Sum: 750]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
[GC Worker Total (ms): Min: 9.6, Avg: 10.3, Max: 17.7, Diff: 8.1, Sum: 134.4]
[GC Worker End (ms): Min: 11311.8, Avg: 11311.8, Max: 11311.9, Diff: 0.1]
[Code Root Fixup: 0.1 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.2 ms]
[Other: 26.9 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 26.4 ms]
[Ref Enq: 0.1 ms]
[Redirty Cards: 0.1 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.1 ms]
[Eden: 240.0M(3680.0M)->0.0B(3664.0M) Survivors: 0.0B->16.0M Heap: 240.0M(12.0G)->15.0M(12.0G)]
Heap after GC invocations=1 (full 0):
garbage-first heap total 12582912K, used 15341K [0x00000004e0800000, 0x00000004e1003000, 0x00000007e0800000)
region size 8192K, 2 young (16384K), 2 survivors (16384K)
Metaspace used 20508K, capacity 21162K, committed 21248K, reserved 1067008K
class space used 2541K, capacity 2793K, committed 2816K, reserved 1048576K
}
[Times: user=0.18 sys=0.01, real=0.05 secs]
2023-09-18T14:11:36.937+0800: 11.339: [GC concurrent-root-region-scan-start]
2023-09-18T14:11:36.937+0800: 11.339: Total time for which application threads were stopped: 0.0454725 seconds, Stopping threads took: 0.0000165 seconds
2023-09-18T14:11:36.956+0800: 11.359: [GC concurrent-root-region-scan-end, 0.0194080 secs]
2023-09-18T14:11:36.956+0800: 11.359: [GC concurrent-mark-start]
2023-09-18T14:11:36.958+0800: 11.361: [GC concurrent-mark-end, 0.0020355 secs]
2023-09-18T14:11:36.958+0800: 11.361: [GC remark 2023-09-18T14:11:36.958+0800: 11.361: [Finalize Marking, 0.0194624 secs] 2023-09-18T14:11:36.978+0800: 11.381: [GC ref-proc, 0.0199802 secs] 2023-09-18T14:11:36.998+0800: 11.401: [Unloading, 0.0062853 secs], 0.0461724 secs]
[Times: user=0.18 sys=0.01, real=0.05 secs]
2023-09-18T14:11:37.005+0800: 11.407: Total time for which application threads were stopped: 0.0465892 seconds, Stopping threads took: 0.0003143 seconds
2023-09-18T14:11:37.005+0800: 11.407: [GC cleanup 26M->26M(12G), 0.0022687 secs]
[Times: user=0.01 sys=0.00, real=0.00 secs]
它是借助了一次young gc进行一次根对象的扫描,有一定的性能优化,对于young gc相关阶段不做赘述,这里记录下其他的阶段:
- initial-mark:初始标记,标记GC Roots
- GC concurrent-root-region-scan-start:主要的目标是扫描年轻代中的Survivor区域以查找指向老年代的引用。这是并发标记循环的早期部分,是在初始标记(STW)之后发生的。
- 扫描Survivor区域:G1 GC并发地扫描Survivor区域,寻找那些指向老年代的引用。
- 标记引用的对象:找到这些引用后,它们指向的老年代中的对象会被标记为存活。
- 非STW阶段:这个阶段并发地与应用程序运行,不会导致应用程序停顿。
- 时效性:此阶段需要在下一次年轻代GC发生之前完成,以确保所有的跨代引用都被正确标记。
- GC concurrent-mark-start:通过可达性分析算法,将根对象引用链上的所有对象标记为存活。
- GC remark:在该阶段中,G1需要一个暂停的时间,去处理剩下的SATB日志缓冲区和所有更新,找出所有未被访问的存活对象,同时安全完成存活数据计算
Mixed GC
2023-09-18T14:23:33.159+0800: 727.562: [GC pause (G1 Evacuation Pause) (mixed)
Desired survivor size 243269632 bytes, new threshold 15 (max 15)
- age 1: 7529224 bytes, 7529224 total
(to-space exhausted), 0.5513782 secs]
[Parallel Time: 354.4 ms, GC Workers: 13]
[GC Worker Start (ms): Min: 727563.0, Avg: 727564.7, Max: 727565.7, Diff: 2.6]
[Ext Root Scanning (ms): Min: 0.2, Avg: 1.1, Max: 4.7, Diff: 4.5, Sum: 14.1]
[Update RS (ms): Min: 10.9, Avg: 13.9, Max: 16.2, Diff: 5.3, Sum: 180.2]
[Processed Buffers: Min: 20, Avg: 84.1, Max: 133, Diff: 113, Sum: 1093]
[Scan RS (ms): Min: 1.8, Avg: 4.5, Max: 5.6, Diff: 3.8, Sum: 58.5]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.1]
[Object Copy (ms): Min: 297.8, Avg: 311.1, Max: 332.0, Diff: 34.3, Sum: 4044.3]
[Termination (ms): Min: 0.0, Avg: 21.4, Max: 33.1, Diff: 33.1, Sum: 278.6]
[Termination Attempts: Min: 1, Avg: 1.6, Max: 9, Diff: 8, Sum: 21]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
[GC Worker Total (ms): Min: 351.0, Avg: 352.0, Max: 353.6, Diff: 2.6, Sum: 4575.9]
[GC Worker End (ms): Min: 727916.6, Avg: 727916.7, Max: 727917.3, Diff: 0.7]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 1.5 ms]
[Other: 195.4 ms]
[Evacuation Failure: 172.2 ms]
[Choose CSet: 0.1 ms]
[Ref Proc: 20.3 ms]
[Ref Enq: 0.1 ms]
[Redirty Cards: 0.5 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.4 ms]
[Free CSet: 0.2 ms]
[Eden: 2232.0M(3672.0M)->0.0B(3680.0M) Survivors: 8192.0K->0.0B Heap: 11.3G(12.0G)->9434.1M(12.0G)]
Heap after GC invocations=38 (full 0):
garbage-first heap total 12582912K, used 9660532K [0x00000004e0800000, 0x00000004e1003000, 0x00000007e0800000)
region size 8192K, 0 young (0K), 0 survivors (0K)
Metaspace used 153978K, capacity 156654K, committed 157000K, reserved 1189888K
class space used 16554K, capacity 17174K, committed 17224K, reserved 1048576K
}
[Times: user=1.49 sys=0.03, real=0.55 secs]
mixed gc过程其实是和年轻代gc差不多的,区别就在于CSet的选择。young gc会选择年轻代所有的Region,mixed gc会根据期望停顿时间来选择最有收益的一批Region集合。这里不在赘述了。
总结
好了,对G1垃圾回收器的学习就暂时到这里了,对其有了大概的印象,包括G1垃圾回收的活动周期(young gc、并发标记周期、多次mixed gc)、RSet、CSet以及卡表等知识有了一定的概念,也学习了如何分析G1的GC日志。上面的内容大多是来源与网上的资料,在一些比较难懂的地方加上自己的理解并绘出直观图和语言注释。
参考
https://pdai.tech/md/java/jvm/java-jvm-gc-g1.html
https://www.infoq.com/articles/G1-one-Garbage-Collector-To-Rule-Them-All/
https://time.geekbang.org/column/intro/100010301
《深入理解Java虚拟机》