章四 JVM调优

介绍JVM调优内容,JVM 调优是为了解决性能瓶颈、优化资源利用和提高系统吞吐量的重要手段。调优主要围绕 内存管理、垃圾回收、线程并发 和 启动性能 等方面展开

调优目标

  1. 响应时间:尽量降低延迟,减少GC停顿时间
  2. 吞吐量:尽量提高系统处理能力,减少GC时间占总时间的比例
  3. 内存使用:优化堆、栈等内存分配,避免内存泄漏和内存溢出

基本流程

  1. 明确调优目标:响应时间、吞吐量或内存占用
  2. 收集基线数据:通过监控工具(如JConsole、VisualVM等)收集cpu、内存、GC日志、线程状态等信息
  3. 发现瓶颈:分析性能问题来源,如GC频繁、线程阻塞和内存不足
  4. 调整JVM参数:逐步调整JVM启动参数,验证效果
  5. 监控和迭代:持续观察调整后的参数,逐步优化

常见参数

  1. 堆内存设置
参数含义
-Xms设置堆的初始大小,建议设置与-Xmx相等,避免动态扩展带来的开销
-Xmx设置堆的最大大小
-XX:NewRatio设置新生代、老年代的比例(默认1:2)
-XX:SurvivorRatio设置 eden和 Survivor比例(默认8:1)
-XX:MaxMetaspaceSize设置元空间最大大小
对于响应时间敏感应用,可以设置较大堆内存,减少GC次数
对于吞吐量优先应用,适当增大新生代,减少对象晋升的频率,减少Minor Gc次数
  1. 垃圾回收器设置
GC 类型参数jdk版本适用场景特点
SerialGc(串行GC)-XX:+UseSerialGCJDK 1.3+单线程、低内存场景,例如简单桌面应用畅叙单线程回收,新生代使用复制算法,老年代使用标记-压缩算法,停顿时间较长,不适合高并发场景
ParallelGc(并发GC)-XX:+UseParallelGCjdk 1.4+(jdk8及以前默认GC)高吞吐量应用多线程回收,吞吐量优先;新生代使用复制算法,老年代使用标记-压缩算法
CMS GC-XX:+UseConcMarkSweepGCjdk 1.4+(jdk15之后已废弃)响应时间优先场景,例如在线交易、金融系统并发标记清除,降低老年代回收停顿时间;存在碎片化问题,容易导致Full GC;JKD9之后被G1替代,JDK14后废弃
G1 GC-XX:+UseG1GCjdk 7+(jdk9之后作为默认GC)大内存(>4GB)、低延迟场景,如试试推荐、游戏服务等面相区域分代收集,避免老年代停顿;支持用户指定最大停顿时间目标,平衡吞吐量和响应时间
ZGC-XX:+UseZGCjdk 11+超低延迟场景,例如高频交易,实时系统停顿时间<10ms,面向超大堆(支持TB级内存);使用染色指针技术避免全局暂停;与G1、CMS相比进一步降低停顿时间
Shenandoah GC-XX:+UseShenandoahGCjdk 12+超低延迟场景,适合低延迟的业务系统,如实时数据处理并发标记-压缩算法,减少老年代碎片化;与ZGC类似,提供更短的停顿时间,堆内存压缩性能更新,比ZGC更适合小型堆(<1TB)
Epsilon GC-XX:+UseEpsilonGCjdk 11+开发和测试场景,无垃圾回收需求,如临时程序或压力测试无垃圾回收起,禁用垃圾回收仅分配内存,内存耗尽直接抛出oom异常
小型应用或单线程任务,使用Serial GC
高吞吐量要求,使用Parallel GC
低延迟应用,优先考虑G1 GC,如果延迟要求极低,选择ZGC或Shenandoah GC
测试或无需回收,使用Epsilon GC
  1. GC日志参数
参数含义
-Xlog:gc启用GC日志输出(jdk>8)
-Xlog:gc*:file=gc.log将GC日志写入文件
-XX:+PrintGCDetails打印详细的GC日志(jdk<= 8)
-XX:+PrintGCApplicationStoppedTime打印程序因GC停顿的时间
-XX:+PrintTenuringDistribution打印对象在新生代中的晋升年龄分布
  1. 线程栈大小
参数含义
-Xss设置线程栈大小
如果线程数较多,适当减少栈大小
如果线程需要深度递归,增大栈大小
  1. JIT优化参数
参数含义
-XX:+TieredCompilation启用混合编译(默认开启)
-XX:CompileThreshold设置JIT热点编译出发阈值(默认10000次)
-XX:+PrintCompilation打印JIT编译的详细信息

优化示例

电商大促期间Full GC频繁案例

问题现象:

  • 大促期间每5分钟发生一次Full GC
  • 每次Full GC停顿2秒以上
  • 年轻代GC频率正常(10秒/次)

分析过程:

# 获取GC日志
jstat -gcutil 18923 1000 10

# 堆内存dump
jmap -dump:live,format=b,file=heap.hprof 18923

诊断结果:

pie
    title 堆内存分布
    "订单缓存对象" : 65
    "会话信息" : 20
    "临时DTO对象" : 10
    "其他" : 5

优化方案:

# 原JVM参数
-Xmx4g -Xms4g -XX:+UseParallelGC

# 优化后参数
-Xmx8g -Xms8g 
-XX:+UseG1GC 
-XX:MaxGCPauseMillis=200 
-XX:InitiatingHeapOccupancyPercent=45
-XX:G1ReservePercent=15
+ 添加本地缓存失效策略

优化效果:

指标优化前优化后
Full GC频率5分钟/次无Full GC
平均响应时间350ms120ms
吞吐量1200TPS4500TPS

大数据处理OOM问题

问题场景:

  • 每日处理千万级数据时发生OOM
  • 错误信息:java.lang.OutOfMemoryError: Java heap space

分析工具:

# 内存对象分析
jmap -histo:live 28471 | head -20

# 堆外内存检查
jcmd 28471 VM.native_memory

内存泄漏定位:

// 问题代码片段
List<DataRecord> buffer = new ArrayList<>();
while((record = readNext()) != null) {
    buffer.add(record); // 未限制缓冲区大小
    process(buffer);
}

优化方案:

  1. 增加批处理限制

    List<DataRecord> buffer = new ArrayList<>(1000);
    while((record = readNext()) != null) {
     buffer.add(record);
     if(buffer.size() >= 1000) {
         process(buffer);
         buffer.clear();
     }
    }
  2. JVM参数调整

    -XX:+UseCompressedOops
    +XX:MaxDirectMemorySize=512m
    -Xmx12g -Xms12g
    -XX:+UseG1GC
    -XX:SurvivorRatio=8

优化效果:

  • 内存使用稳定在8GB以内
  • 处理速度提升30%
  • 无OOM发生

高并发服务GC调优

系统特征:

  • 100+节点微服务集群
  • 平均QPS 2万+/节点
  • 响应时间波动大(50ms~2s)

GC日志分析:

[GC pause (G1 Evacuation Pause) (young), 0.2308990 secs]
   [Parallel Time: 220.3 ms, GC Workers: 16]
      [Ext Root Scanning (ms): 45.6]
      [Update RS (ms): 32.1]
        [Processed Buffers: 1200]
      [Scan RS (ms): 12.4]
      [Code Root Scanning (ms): 1.2]
      [Object Copy (ms): 128.7]
      [Termination (ms): 0.3]

调优步骤:

  1. 调整Region大小

    -XX:G1HeapRegionSize=4m 
    +XX:G1HeapRegionSize=8m
  2. 优化并行阶段

    -XX:ConcGCThreads=4 
    +XX:ConcGCThreads=8
    -XX:ParallelGCThreads=16
    +XX:ParallelGCThreads=24
  3. 内存分配优化

    -XX:+UseTLAB
    +XX:TLABSize=128k
    -XX:-ResizeTLAB 
    +XX:+ResizeTLAB

效果对比:

参数优化前停顿优化后停顿
Young GC230ms150ms
Mixed GC450ms280ms
吞吐量损失12%6%

内存泄漏排查案例

现象描述:

  • 服务运行3天后内存占用达95%
  • 重启后内存增长曲线异常

排查工具组合:

  1. 即时分析

    jcmd 38742 GC.class_histogram | grep -v "java.lang"
  2. 趋势监控

    jstat -gcutil 38742 60s
  3. 堆转储分析

    // 使用MAT分析找到的泄漏对象
    Dominator Tree -> Thread Local Storage

泄漏代码定位:

// 错误使用ThreadLocal
private static ThreadLocal<SimpleDateFormat> formatter = 
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

// 正确写法
private static final ThreadLocal<SimpleDateFormat> formatter = 
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

修复效果:

graph LR
    A[运行时间] --> B[内存占用]
    A1(24小时) --> B1(45%)
    A2(48小时) --> B2(48%)
    A3(72小时) --> B3(50%)

ZGC低延迟场景实践

业务需求:

  • 金融交易系统
  • 要求单笔交易延迟<10ms
  • 99.99%的GC停顿<1ms

基准测试:

# 使用SPECjbb2015测试
java -XX:+UseZGC -Xmx16g -Xms16g \
     -XX:ConcGCThreads=8 \
     -XX:NativeMemoryTracking=detail \
     -jar specjbb2015.jar

参数调优:

-XX:+UseZGC
-Xms24g -Xmx24g
+XX:ConcGCThreads=6
+XX:SoftMaxHeapSize=20g
+XX:AllocatePrefetchStyle=1
+XX:-UsePerCPUMembank

性能指标:

指标Parallel GCG1 GCZGC
最大停顿时间1200ms450ms0.8ms
吞吐量损失8%12%4%
内存占用18GB16GB20GB
99.9%延迟35ms25ms8ms

容器环境调优实践

典型问题:

  • Kubernetes Pod频繁OOMKilled
  • 容器内存限制8GB,但实际使用超限

关键配置:

# 容器规范
resources:
  limits:
    memory: "8Gi"
    cpu: "4"
    
# JVM参数优化
-XX:+UseContainerSupport
+XX:MaxRAMPercentage=75.0
+XX:ActiveProcessorCount=4
-XX:+AlwaysPreTouch

内存分布优化:

pie
    title 容器内存分配
    "JVM堆" : 6144
    "堆外内存" : 1024
    "元空间" : 256
    "系统预留" : 512

监控方案:

# 容器内存监控
kubectl top pod

# JVM内部监控
jcmd 1 VM.native_memory summary

优化效果:

  • 内存使用稳定在7.5GB以内
  • 无OOMKilled发生
  • GC频率降低40%

    本文由博客群发一文多发等运营工具平台 OpenWrite 发布

树荫下的影
3 声望0 粉丝

« 上一篇
JVM基础
下一篇 »
JMM