如何有效分析和优化JVM性能:从GC日志到调优技巧的完整指南
前言
不久前,一位读者在阿里巴巴的面试中遇到了GC日志分析的问题,虽然他部分回答了问题,但未能抓住关键点。因此,本文将深入探讨如何利用JDK的现有工具和可视化工具来分析GC日志,并进行JVM调优。
JVM调优实践
主要步骤
默认的策略虽然常用,但并不一定是最佳选择。
- 监控与分析GC日志
- 判断JVM问题:
- 如果参数设置合理,且系统没有超时日志,GC频率和耗时都在可接受范围内,则无需进行优化。
- 反之,如果GC时间超过1-3秒,或者频繁执行GC,则必须进行优化。
- 确定调优目标
- 调整参数
- 调优通常从满足程序的内存使用开始,接着是时间延迟要求,最后是吞吐量要求。这一过程必须循序渐进。
- 对比调优前后的差异
- 重复步骤:1、2、3、4、5,直至找到最佳的JVM参数设置。
- 将JVM参数应用于应用服务器:
- 找到最合适的参数并应用到所有服务器上,同时进行后续跟踪。
以上,是我们进行JVM调优的基本步骤。现在,我们就从第一步开始吧!
GC日志分析
初始参数设置
机器环境:
指标 | 参数 |
---|---|
机器 | CPU 12 核,内存 16GB |
集群规模 | 单机 |
seqb_web 版本 | 1.0 |
数据库 | 4 核 16G |
典型的JVM调优参数设置如下:
- -Xms 堆内存的最小值:堆内存会根据使用情况自动调整,直到达到-Xmx设定的最大值。
- -Xmx 堆内存的最大值:默认为总内存/64(小于1G),可根据需求调整。
- -Xmn 新生代内存的最大值:包括Eden区和两个Survivor区的总和,通过参数配置。
- -Xss 每个线程的栈内存:默认1M,一般不需修改,栈越小可创建的线程数越多。
整个堆的大小为年轻代大小加上老年代大小,建议在开发环境中分别设置Xms和Xmx,但在生产环境中,这两个值应保持一致,以避免波动。
这里需要特别注意,一般情况下,我们建议将Xms和Xmx的值设置为相同!
要确保默认配置没有性能瓶颈,进一步分析GC日志是关键。
- -XX:+PrintGCDetails:开启GC日志以获取更详细的信息。
- -XX:+PrintGCTimeStamps,-XX:+PrintGCDateStamps:开启GC时间和日期提示。
- -XX:+PrintHeapAtGC:打印堆的GC日志。
- -Xloggc:./logs/gc.log:指定GC日志的存储路径。
在Windows环境中,IDEA的配置如下:
配置完成后,日志将生成在E盘的logs文件夹中。
GC日志解读
Young GC日志分析:
2022-08-05T13:45:23.336+0800: 4.866: [GC (Metadata GC Threshold) [PSYoungGen: 136353K->20975K(405504K)] 160049K->48437K(720384K), 0.0092260 secs] [Times: user=0.00 sys=0.02, real=0.02 secs]
逐项解析如下:
2022-08-05T13:45:23.336+0800: 本次GC发生的时间
4.866: 应用启动的时间
[GC] GC的类型(youngGC)
(Metadata GC Threshold) 表示元空间超阈值
[PSYoungGen: 136353K->20975K(405504K年轻代总空间)] 160049K->48437K(720384K) 整个堆的状态
0.0092260 secs 表示本次垃圾回收的耗时
[Times: user=0.00 sys=0.02, real=0.02 secs] 本次GC消耗的CPU时间及系统暂停时间
以上解析应该足够详细,大家是不是更容易理解了呢?
Full GC日志分析:
2022-08-05T20:24:47.815+0800: 6.955: [Full GC (Metadata GC Threshold) [PSYoungGen: 701K->0K(72704K)] [ParOldGen: 38678K->35960K(175104K)] 39380K->35960K(247808K), [Metaspace: 56706K->56706K(1099776K)], 0.1921975 secs] [Times: user=1.03 sys=0.00, real=0.19 secs]
逐项解析如下:
2022-08-05T20:24:47.815+0800: 本次GC的发生时间
6.955: 服务启动后到Full GC的时间
[Full GC (Metadata GC Threshold) 表示元空间超出阈值
[PSYoungGen: 701K->0K(72704K)] 年轻代没有回收空间
[ParOldGen: 38678K->35960K(175104K)] 老年代没有达到阈值
39380K->35960K(247808K) 表示整个堆的状态
[Metaspace: 56706K->56706K(1099776K)], 0.1921975 secs 表示本次GC的耗时
[Times: user=1.03 sys=0.00, real=0.19 secs] 本次GC消耗的CPU时间及系统暂停时间
有些朋友可能会觉得日志内容太复杂,难以理解。
GC日志可视化分析
为了更有效地分析GC日志,建议将GC日志输出到一个文件中,并使用GC日志分析工具(如gceasy.io)进行分析。
完成分析后,可以下载生成的分析报告。
1) JVM内存占用情况:
Generation【区域】 | Allocated【最大值】 | Peak【占用峰值】 |
---|---|---|
Young Generation【年轻代】 | 74.5 mb | 74.47 mb |
Old Generation【老年轻代】 | 171 mb | 95.62 mb |
Meta Space【元空间】 | 1.05 gb | 55.38 mb |
Young + Old + Meta space【整体】 | 1.3 gb | 212.64 mb |
2) 关键性能指标:
-
吞吐量:越高表示GC开销越低,反映JVM的吞吐量。
- Throughput:97.043%
-
GC延迟:
- Avg Pause GC Time:7.80 ms(平均GC暂停时间)
- Max Pause GC Time:190 ms(最大GC暂停时间)
3) GC可视化交互聚合结果
从图中可以看到,发生了3次Full GC,这显然是不正常的。
4) GC统计
GC Statistics:提供GC的总次数、young GC与full GC的统计及GC暂停时间的统计。
5) GC原因分析:
原因 | 次数 | 平均时间 | 最大时间 | 总耗时 |
---|---|---|---|---|
Metadata GC Threshold | 6 | 43.3 ms | 190 ms | 260 ms |
Allocation Failure | 53 | 3.77 ms | 10.0 ms | 200 ms |
分析上面的原因可以看出:
- Metadata GC Threshold:元空间超出阈值。
- Allocation Failure:年轻代空间不足。
值得补充的是,可能还存在另一种原因:
- Ergonomics:GC中的自动调节机制,用于在GC暂停时间和吞吐量之间取得平衡,旨在优化虚拟机性能。
通过可视化工具,我们可以快速分析GC日志,善用这些工具非常重要,因为GC日志通常内容浩繁,难以把握。使用GC Easy这类在线工具,可以显著提高工作效率,而且解析GC日志是免费的。