gc 日志是 jvm 性能调优的重要依据,不同垃圾收集器(如 cms、g1、zgc)的日志格式有所差异,但核心指标含义一致。
以下是对 gc 日志中各项指标的详细解读:
一、基础 gc 日志格式(以 g1 为例)
1. minor gc 日志
[gc pause (g1 evacuation pause) (young), 0.0144227 secs] [parallel time: 13.0 ms, gc workers: 8] [eden: 24.0m(24.0m)->0.0b(20.0m) survivors: 0.0b->4.0m heap: 24.0m(256.0m)->20.4m(256.0m)] [times: user=0.09 sys=0.00, real=0.01 secs]
2. full gc 日志
[full gc (metadata gc threshold) [psyounggen: 512k->0k(9216k)] [paroldgen: 6336k->6848k(10240k)] 6848k->6848k(19456k), [metaspace: 2560k->2560k(1056768k)], 0.0254310 secs] [times: user=0.09 sys=0.00, real=0.03 secs]
二、关键指标解析
1. gc 类型与触发原因
[gc pause (g1 evacuation pause) (young) [full gc (metadata gc threshold)
gc 类型:
gc
:minor gc(新生代回收)full gc
:全堆回收(可能包含新生代、老年代、元空间)
触发原因:
allocation failure
:对象分配失败(eden 区空间不足)metadata gc threshold
:元空间达到阈值g1 humongous allocation
:大对象直接分配到老年代
2. 堆内存变化
[eden: 24.0m(24.0m)->0.0b(20.0m) survivors: 0.0b->4.0m heap: 24.0m(256.0m)->20.4m(256.0m)]
格式:区域名: 回收前使用量(总容量)->回收后使用量(新总容量)
关键指标:
- eden 区:gc 后通常被清空(0.0b)
- survivor 区:对象在两个 survivor 区之间移动
- heap:整个堆的使用情况(回收前 24.0m → 回收后 20.4m)
3. gc 耗时
0.0144227 secs
stw 时间:应用线程暂停的时间(本例中 14.4ms)
异常情况:
- 单次 gc 时间过长(如超过 500ms)
- 频繁 gc 导致应用响应缓慢
4. 并行 / 并发阶段
[parallel time: 13.0 ms, gc workers: 8] [concurrent marking (10.2 ms)]
- parallel:多线程并行执行的阶段
- concurrent:与应用线程并发执行的阶段(不产生 stw)
- gc workers:并行 gc 线程数
5. 对象晋升与年龄分布
[tenuring distribution] age 1: 1310720 bytes, 1310720 total age 2: 655360 bytes, 1966080 total
age n:存活了 n 次 gc 的对象大小
异常情况:
- 大量对象在低年龄(如 age 1)就晋升到老年代
- 晋升年龄分布不均匀(如 age 15 突然激增)
6. cpu 时间
[times: user=0.09 sys=0.00, real=0.01 secs]
- user:所有 gc 线程在用户态消耗的 cpu 时间总和
- sys:gc 线程在内核态消耗的 cpu 时间
- real:实际流逝的时钟时间
- 关系:
user + sys ≈ real × 并行线程数
7. 元空间 / 永久代
[metaspace: 2560k->2560k(1056768k)]
- 元空间:存储类元数据(jdk 8+)
- 永久代:类似元空间(jdk 7 及以前)
- 异常情况:元空间持续增长,可能触发 full gc
三、不同收集器的日志差异
1. g1 收集器
[gc pause (g1 evacuation pause) (young), 0.0144227 secs] [gc pause (g1 humongous allocation), 0.5123456 secs]
关键指标:
- humongous 对象分配(直接在老年代分配的大对象)
- mixed gc(同时回收新生代和部分老年代)
2. cms 收集器
[gc (allocation failure) [parnew: 61440k->61440k(61440k), 0.0000388 secs] [cms: 0k->20480k(102400k), 0.0123456 secs] 61440k->20480k(163840k), [metaspace: 2560k->2560k(1056768k)] 0.0123844 secs]
关键阶段:
- initial mark(初始标记,stw)
- concurrent marking(并发标记)
- remark(重新标记,stw)
- concurrent sweep(并发清除)
3. parallel 收集器
[gc (allocation failure) [psyounggen: 8192k->512k(9216k)] 8192k->6848k(19456k), 0.0034949 secs]
特点:
- 仅包含并行阶段
- 重点关注吞吐量(而非低延迟)
四、异常指标与问题定位
1. 频繁 full gc
日志特征:
[full gc (metadata gc threshold) ... 0.5234120 secs]
可能原因:
- 老年代空间不足
- 元空间持续增长
- 内存泄漏
2. 长时间 stw
日志特征:
[gc pause (g1 humongous allocation) ... 1.2345678 secs]
可能原因:
- 堆内存过大
- cms 并发模式失败
- 大量 humongous 对象
3. 过早晋升
日志特征:
[tenuring distribution] age 1: 104857600 bytes, ...
可能原因:
- survivor 区空间不足
- 对象分配率过高
-xx:maxtenuringthreshold
设置过小
五、分析工具与实战建议
1. 命令行工具
# 统计gc次数 grep "gc" gc.log | wc -l # 计算平均gc耗时 awk '/gc.*secs/ {sum+=$nf} end {print "average gc time: " sum/nr " secs"}' gc.log # 提取堆使用情况 grep "\[heap\]" gc.log | awk '{print $6, $8}' > heap_usage.txt
2. 可视化工具
- gceasy(在线):上传 gc 日志生成详细报告
- gcviewer(本地):分析 gc 频率、内存趋势
- java mission control:集成 gc 日志分析和飞行记录器
3. 关键分析步骤
- 确认 gc 类型:区分 minor gc、major gc、full gc
- 计算 gc 频率:每分钟 / 小时发生的 gc 次数
- 分析内存趋势:堆内存使用量是否持续增长
- 检查 gc 耗时:单次 gc 时间是否超过预期
- 定位 gc 原因:allocation failure、metadata threshold 等
六、优化建议
1. 减少 full gc
# 增大元空间 java -xx:metaspacesize=256m -xx:maxmetaspacesize=512m yourapp # 避免大对象直接进入老年代 java -xx:pretenuresizethreshold=1048576 yourapp
2. 降低 gc 耗时
# 使用g1收集器并限制最大停顿时间 java -xx:+useg1gc -xx:maxgcpausemillis=200 yourapp # 增加并行gc线程数 java -xx:parallelgcthreads=16 yourapp
3. 优化对象晋升
# 增大survivor区比例 java -xx:survivorratio=6 yourapp # 提高晋升年龄阈值 java -xx:maxtenuringthreshold=15 yourapp
通过深入理解 gc 日志中的各项指标,可以精准定位内存泄漏、对象分配不合理、收集器选择不当等问题,从而优化 jvm 配置,提升应用性能。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论