如何有效分析和优化JVM性能:从GC日志到调优技巧的完整指南

前言

不久前,一位读者在阿里巴巴的面试中遇到了GC日志分析的问题,虽然他部分回答了问题,但未能抓住关键点。因此,本文将深入探讨如何利用JDK的现有工具和可视化工具来分析GC日志,并进行JVM调优。

JVM调优实践

主要步骤

默认的策略虽然常用,但并不一定是最佳选择。

  1. 监控与分析GC日志
  2. 判断JVM问题
    • 如果参数设置合理,且系统没有超时日志,GC频率和耗时都在可接受范围内,则无需进行优化。
    • 反之,如果GC时间超过1-3秒,或者频繁执行GC,则必须进行优化。
  3. 确定调优目标
  4. 调整参数
    • 调优通常从满足程序的内存使用开始,接着是时间延迟要求,最后是吞吐量要求。这一过程必须循序渐进。
  5. 对比调优前后的差异
  6. 重复步骤:1、2、3、4、5,直至找到最佳的JVM参数设置。
  7. 将JVM参数应用于应用服务器
    • 找到最合适的参数并应用到所有服务器上,同时进行后续跟踪。

以上,是我们进行JVM调优的基本步骤。现在,我们就从第一步开始吧!

GC日志分析

初始参数设置

机器环境

指标参数
机器CPU 12 核,内存 16GB
集群规模单机
seqb_web 版本1.0
数据库4 核 16G

典型的JVM调优参数设置如下:

  1. -Xms 堆内存的最小值:堆内存会根据使用情况自动调整,直到达到-Xmx设定的最大值。
  2. -Xmx 堆内存的最大值:默认为总内存/64(小于1G),可根据需求调整。
  3. -Xmn 新生代内存的最大值:包括Eden区和两个Survivor区的总和,通过参数配置。
  4. -Xss 每个线程的栈内存:默认1M,一般不需修改,栈越小可创建的线程数越多。

整个堆的大小为年轻代大小加上老年代大小,建议在开发环境中分别设置Xms和Xmx,但在生产环境中,这两个值应保持一致,以避免波动。

这里需要特别注意,一般情况下,我们建议将Xms和Xmx的值设置为相同!

图片

要确保默认配置没有性能瓶颈,进一步分析GC日志是关键。

  1. -XX:+PrintGCDetails:开启GC日志以获取更详细的信息。
  2. -XX:+PrintGCTimeStamps,-XX:+PrintGCDateStamps:开启GC时间和日期提示。
  3. -XX:+PrintHeapAtGC:打印堆的GC日志。
  4. -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 mb74.47 mb
Old Generation【老年轻代】171 mb95.62 mb
Meta Space【元空间】1.05 gb55.38 mb
Young + Old + Meta space【整体】1.3 gb212.64 mb

2) 关键性能指标:

图片

  1. 吞吐量:越高表示GC开销越低,反映JVM的吞吐量。

    • Throughput:97.043%
  2. 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 Threshold643.3 ms190 ms260 ms
Allocation Failure533.77 ms10.0 ms200 ms

分析上面的原因可以看出:

  1. Metadata GC Threshold:元空间超出阈值。
  2. Allocation Failure:年轻代空间不足。

值得补充的是,可能还存在另一种原因:

  • Ergonomics:GC中的自动调节机制,用于在GC暂停时间和吞吐量之间取得平衡,旨在优化虚拟机性能。

通过可视化工具,我们可以快速分析GC日志,善用这些工具非常重要,因为GC日志通常内容浩繁,难以把握。使用GC Easy这类在线工具,可以显著提高工作效率,而且解析GC日志是免费的。