Java虚拟机 系列文章
Java虚拟机1 内存管理、GC,包括 Shenandoah ZGC
Java虚拟机2 G1垃圾回收详解, 参数, 日志 (本文)
Java虚拟机3 Class文件及类加载
Java虚拟机4 方法调用原理、动态类型支持
Java虚拟机5 编译与优化
Java虚拟机6 内存模型、线程、锁
总结 Java 不支持的语法特性
Java 协程:Loom Project 实战
其他JVM语言
垃圾回收(GC)算法
哪些对象需要被回收?
引用计数
可达性分析
回收垃圾对象
标记清除 Mark-Sweep
标记复制 Mark-Copy
标记整理 Mark-Compact
分代收集
新生代(Young)、老年代(Old)
并行(Parallel)与并发(Concurrent)
其他GC概念
根节点(Root)
安全点(Safe-Point)
记忆集(R-Set)
写屏障/读屏障
三色标记法
分配内存
指针碰撞
空闲链表
G1 垃圾回收(Garbage-First)
官方介绍
https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html
堆内存
G1采用分代回收,将整个堆拆成多个分区(Heap Region)。一个分区既可以充当新生代可也以充当老年代。
G1的分区类型(HeapRegionType)大致可以分为四类:
- 自由分区(Free Heap Region,FHR)
- 新生代分区(Young Heap Region,YHR)
- 大对象分区(Humongous Heap Region,HHR)
- 老生代分区(Old Heap Region,OHR)
其中新生代分区又可以分为Eden和Survivor;大对象分区又可以分为大对象头分区和大对象连续分区。
每个分区大小相同,区间为[1M, 32M], 且为
2
n
2^{n}
2n。默认分为2048个分区。
大对象:大于单个分区的一半。
三色并发标记
新生代回收(Young GC)
回收整个新生代
并行复制算法
会动态改变新生代大小
混合回收(Mixed GC)
回收整个新生代和部分老年代
优先回收垃圾多的老年代
并行复制算法
Full GC
回收全部分区
G1停顿预测模型
期望停顿时间: MaxGCPauseMillis,默认200ms,尽量在这个停顿时间内。
根据历史数据计算本次收集的分区数量。
期望停顿时间越小,新生代空间越小。
记忆集
记录分区之间的引用关系(谁引用我)
用到写屏障
参数
注: *标记的为实验选项,需要打开 -XX:+UnlockExperimentalVMOptions
参数 | 默认值 | 说明 | 优化 |
---|---|---|---|
xms | 初始堆大小 | 根据业务设置 | |
xmx | 最大堆大小 | 根据业务设置 | |
+UseG1GC | 开启G1 GC | ||
堆内存 | |||
MaxGCPauseMillis | 200ms | 最大停顿时间,不是硬性条件 | 根据业务设置 |
G1HeapRegionSize | 0 | 设置Region大小,[1M,32M]不设置时启发式推断 | 可以逐步验证设置 |
G1NewSizePercent | 5 | 新生代最小百分比 | 一般不设置,影响暂停时间 |
G1MaxNewSizePercent | 60 | 新生代最大百分比 | 一般不设置,影响暂停时间 |
GCTimeRatio | 9 | GC与应用程序之间的时间占比 | 可以逐步验证设置 |
G1ExpandByPercentOfAvailable | 20 | 一次扩展的比例 | 可以逐步验证设置 |
G1ReservePercent | 10 | 用于对象晋升到老年代的保留内存比例,最大为50 | 可以逐步验证设置 |
*G1NewSizePercent | Eden分区占比 | ||
GCPauseIntervalMillisGC | 0 | GC间隔时间,不设置会启发式推断 | |
G1ConfidencePercent | GC预测置信度,越小代表预测越准确 | ||
新生代回收 | |||
ParallelGCThreads | STW期间,并行GC线程数,会根据CPU核数推断 | ||
MaxTenuringThreshold | 15 | 达到该年龄时晋升到老年代,最大为15 | 可以逐步验证设置 |
G1RsetScanBlockSize | 64 | 扫描记忆集时一次处理的量 | |
SurvivorRatio | 8 | Eden和一个Survivor的比例,会影响Survivor大小 | |
TargetSurvivorRatio | 50 | 期望Survivor大小比例,增大该值会降低晋升到老年代概率 | |
ParGCArrayScanChunk | 50 | 对象数组每次遍历长度阈值 | |
+G1EagerReclaimHumongousObjects | true | 是否在YGC时回收大对象,可以关闭 | |
+G1EagerReclaimHumongousObjectsWithStaleRefs | true | 判定收集哪些大对象,false表示只有记忆集引用数为0才收集 | |
并发标记 | |||
ConcGCThreads | 0 | 并发标记线程数,不设置则动态调整 | |
InitiatingHeapOccupancyPercent | 45 | 触发并发标记的整个堆使用率阈值 | |
G1ConcMarkStepDurationMillis | 10 | 并发标记子阶段每次最多执行时间 | 可以逐步验证设置 |
G1SATBBufferSize | 1K | 表示每个原始快照队列最多存放1000个灰色对象 | |
G1SATBBufferEnqueueingThresholdPercent | 60 | 队列过滤后超过该阈值则新分配一个队列 | |
MarkStackSize | 4M | 并发标记子阶段中用到的标记栈的大小,不设置则启发式推断 | |
MarkStackSizeMax | 512M | 并发标记子阶段中用到的标记栈的大小,不设置则启发式推断 | |
GCDrainStackTargetSize | 64 | 一次标记的最多对象个数 | |
混合回收 | |||
HeapSizePerGCThread | 64M | 可以简单地理解为每64M分配一个线程 | |
-UseDynamicNumberOfGCThreads | false | 是否动态调整GC线程数 | |
-ForceDynamicNumberOfGCThreads | false | 是否动态调整GC线程数 | |
G1MixedGCLiveThresholdPercent | 85 | 判断分区能否加入CSet,低于该百分比则加入 | |
G1HeapWastePercent | 5 | CSet中可回收空间比例大于改值才会开始混合收集 | |
G1MixedGCCountTarget | 8 | 值越大,收集老年代分区越少 | |
G1OldCSetRegionThresholdPercent | 10 | 表示一次最多收集10%的分区 | |
ClassUnloadingWithConcurrentMark | true | 打开表示在并发标记的时候可以卸载已经加载的类 | |
TLAB | 该类参数一般不设置 | ||
+UseTLAB | true | 是否开启TLAB | |
TLABSize | TLAB大小 | ||
TLABWasteTargetPercent | 1 | TLAB可占用的Eden空间的百分比 | 线程数很多可以增大 |
TLABRefillWasteFraction | 64 | TLAB允许浪费的比例 | |
TLABWasteIncrement | 4 | 动态的增加浪费空间的字节数 | |
MinTLABSize | 2k | TLAB最小值,可以根据实际应用设置 | |
+ResizeTLAB | true | 是否允许自动调整TLAB大小 | |
PLAB | |||
+ResizePLAB | true | 是否自动调整PLAB大小 | 可以关闭 |
YoungPLABSize | 4096 | 新生代PLAB缓存大小 | 可以减小 |
OldPLABSize | 1024 | 老年代PLAB缓存大小 | 可以增大 |
ParallelGCBufferWastePct | 10 | PLAB缓存可浪费比例 | |
记忆集 | 该类参数一般不设置 | ||
G1ConcRefinementThreads | 0 | ||
G1UpdateBufferSize | 256 | ||
+G1UseAdaptiveConcRefinement | true | ||
G1RSetUpdatingPauseTimePercent | 10 | ||
G1ConcRefinementThresholdStep | 0 | ||
G1ConcRefinementServiceIntervalMillis | 300 | ||
G1ConcRefinementGreenZone | 0 | ||
G1ConcRefinementYellowZone | 0 | ||
G1ConcRefinementRedZone | 0 | ||
G1ConcRSLogCacheSize | 10 | ||
G1ConcRSHotCardLimit | 4 | ||
G1RSetRegionEntries | 0 | ||
引用 | |||
+ParallelRefProcBalancingEnabled | true | 同一引用类型多个队列之间的均衡 | |
-ParallelRefProcEnabled | false | 是否并发处理引用,可以打开 | |
+RegisterReferences | true | 遍历对象是判断引用类型是否可回收,一般不设置 | |
*+G1UseConcMarkReferenceProcessing | true | 是否在并发标记的时候标记引用 | |
RefDiscoveryPolicy | 0 | 0或1,配1表示引用对象里面的对象在处理范围内时,也处理该引用对象 | |
SoftRefLRUPolicyMSPerMB | 1000 | 软引用存活时间,1000表示每MB的内存将会存活1s | |
字符串去重 | |||
-UseStringDeduplication | false | 是否打开字符串去重 | 验证有效后再打开 |
StringDeduplicationAgeThreshold | 3 | 控制字符串是否参与去重的阈值 | |
打印日志 | |||
+PrintGC | GC 基本信息 | ||
+PrintGCDetails | GC 详细信息 | ||
*G1LogLevel=finest | GC 最详细信息 | ||
+PrintGCApplicationConcurrentTime | 打印程序执行时间 | ||
+PrintGCApplicationStoppedTime | 打印停顿时间 | ||
+PrintAdaptiveSizePolicy | 打印参数自动设置 | ||
+PrintCommandLineFlags | 打印设置过的参数 | ||
+PrintTLAB | 跟踪TLAB使用情况 | ||
+G1TraceConcRefinement | 记忆集线程工作情况 | ||
+G1SummarizeRSetStats | 记忆集信息 | ||
*+G1TraceEagerReclaimHumongousObjects | 大对象收集信息 | ||
+PrintTenuringDistribution | 对象年龄信息 | ||
+PrintReferenceGC | 引用GC信息 | ||
+G1PrintHeapRegions | 内存的分配和使用情况 | ||
+PrintGCTimeStamps | 每条日志附加时间戳(程序启动到现在的时间) | ||
+PrintGCDateStamps | 每条日志附加当前时间 | ||
其他 | |||
-DisableExplicitGC | 禁止显式调用GC | ||
+OmitStackTraceInFastThrow | true | 同一位置连续抛出异常时快速抛出,此时没有异常堆栈 | |
+AlwaysPreTouch | 程序启动时分配所有物理内存,而不是使用时再分配 | ||
+UnlockExperimentalVMOptions | 开启实验选项 |
触发 Full GC 可能有这几个原因:
-
没有足够的空间用于晋升,可:
-
增加-XX:G1HeapRegionSize。
-
增加-XX:G1ReservePercent选项的值(并相应增加总的堆大小),为“目标空间”增加预留内存量。
-
并发标记不够及时,垃圾没有及时回收,所以调整的思路应该是尽早启动并发标记,让并发标记尽早完成,可:
-
通过减少- XX:InitiatingHeapOccupancyPercent提前启动标记周期。
-
也可以通过增加- XX:ConcGCThreads选项的值来增加并行标记线程的数目。
-
增加- XX:G1MixedGCCountTarget的值,使得老生代回收的分区减少。
-
减少- XX:G1ConcMarkStepDurationMillis的值,让并发标记更频繁,Remark时间更短等。
日志
// young 表示新生代回收,STW时间为 0.0170949 secs
2020-05-27T13:01:38.244+0800: 251872.929: [GC pause (G1 Evacuation Pause) (young), 0.0170949 secs]
// 并行GC线程,13个,时间为15.9ms
[Parallel Time: 15.9 ms, GC Workers: 13]
// Diff:这13个线程开始最大时间差,反应进入安全点的情况
[GC Worker Start (ms): Min: 251872929.6, Avg: 251872929.7, Max: 251872929.7, Diff: 0.1]
// 根扫描时间
[Ext Root Scanning (ms): Min: 0.7, Avg: 0.9, Max: 2.1, Diff: 1.4, Sum: 11.7]
// 更新记忆集时间
[Update RS (ms): Min: 0.7, Avg: 1.8, Max: 2.0, Diff: 1.3, Sum: 23.8]
// buffer 个数
[Processed Buffers: Min: 8, Avg: 19.5, Max: 37, Diff: 29, Sum: 253]
// 扫描记忆集查找被引用的对象
[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
// 扫描JIT编译代码中引用
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
// 存活对象复制到新分区
[Object Copy (ms): Min: 12.9, Avg: 12.9, Max: 12.9, Diff: 0.1, Sum: 167.9]
// 停止GC线程时间
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 13]
// 并行处理时其他处理花费的时间
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.3]
// 并行GC花费的总体时间
[GC Worker Total (ms): Min: 15.6, Avg: 15.7, Max: 15.7, Diff: 0.1, Sum: 203.8]
// Diff: GC线程结束最大时间差
[GC Worker End (ms): Min: 251872945.3, Avg: 251872945.3, Max: 251872945.4, Diff: 0.0]
// 修复和清理Code Root
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
// 清除记忆集中的卡表
[Clear CT: 0.4 ms]
[Other: 0.9 ms]
// 选择回收区域, YoungGC是0
[Choose CSet: 0.0 ms]
// 处理引用
[Ref Proc: 0.3 ms]
// 引用重新加入队列
[Ref Enq: 0.0 ms]
// 重构记忆集
[Redirty Cards: 0.2 ms]
// 处理大对象
[Humongous Register: 0.0 ms]
// 回收大对象
[Humongous Reclaim: 0.0 ms]
// 释放已回收的记忆集
[Free CSet: 0.0 ms]
// Eden变为0,下一次使用空间为1184.0M, Survivors为 49152K, 整个堆变为523M
[Eden: 944.0M(944.0M)->0.0B(944.0M) Survivors: 49152.0K->49152.0K Heap: 1467.7M(2048.0M)->523.0M(2048.0M)]
// user GC 线程使用 CPU 时间
// sys 内核使用 CPU 时间
// real 从开始到结束总时间
// user 和 sys 为每个线程使用的时间总和,所以可能大于 real
[Times: user=0.13 sys=0.08, real=0.02 secs]
// initial-mark 表示此次YGC后开始并发标记
2020-05-24T15:03:59.990+0800: 14.675: [GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0473590 secs]
// 开始并发标记根扫描
2020-05-24T15:04:00.037+0800: 14.723: [GC concurrent-root-region-scan-start]
// 根扫描结束
2020-05-24T15:04:00.080+0800: 14.766: [GC concurrent-root-region-scan-end, 0.0431292 secs]
// 并发扫描整个堆
2020-05-24T15:04:00.080+0800: 14.766: [GC concurrent-mark-start]
// 扫描结束
2020-05-24T15:04:00.081+0800: 14.766: [GC concurrent-mark-end, 0.0001221 secs]
// 再标记、处理引用和类卸载, 会STW
2020-05-24T15:04:00.081+0800: 14.766: [GC remark 2020-05-24T15:04:00.081+0800: 14.766: [Finalize Marking, 0.0008773 secs] 2020-05-24T15:04:00.082+0800: 14.767: [GC ref-proc, 0.0008966 secs] 2020-05-24T15:04:00.083+0800: 14.768: [Unloading, 0.0100973 secs], 0.0121943 secs]
[Times: user=0.14 sys=0.00, real=0.01 secs]
// 清除,调整记忆集, 会STW
2020-05-24T15:04:00.093+0800: 14.779: [GC cleanup 104M->104M(2048M), 0.0009312 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
// mixed表示混合回收
2020-05-27T14:38:36.037+0800: 257690.722: [GC pause (G1 Evacuation Pause) (mixed), 0.0614190 secs]
// 内存分配失败导致 FULL GC
Full GC (Allocation Failure)
[Eden: 3072.0K(194.0M)->0.0B(201.0M) Survivors: 0.0B->0.0B Heap: 3727.1M(4022.0M)->3612.0M(4022.0M)], [Metaspace: 2776K->2776K(1056768K)]