章四 JVM调优
介绍JVM调优内容,JVM 调优是为了解决性能瓶颈、优化资源利用和提高系统吞吐量的重要手段。调优主要围绕 内存管理、垃圾回收、线程并发 和 启动性能 等方面展开
调优目标
- 响应时间:尽量降低延迟,减少GC停顿时间
- 吞吐量:尽量提高系统处理能力,减少GC时间占总时间的比例
- 内存使用:优化堆、栈等内存分配,避免内存泄漏和内存溢出
基本流程
- 明确调优目标:响应时间、吞吐量或内存占用
- 收集基线数据:通过监控工具(如JConsole、VisualVM等)收集cpu、内存、GC日志、线程状态等信息
- 发现瓶颈:分析性能问题来源,如GC频繁、线程阻塞和内存不足
- 调整JVM参数:逐步调整JVM启动参数,验证效果
- 监控和迭代:持续观察调整后的参数,逐步优化
常见参数
- 堆内存设置
参数 | 含义 |
---|---|
-Xms | 设置堆的初始大小,建议设置与-Xmx相等,避免动态扩展带来的开销 |
-Xmx | 设置堆的最大大小 |
-XX:NewRatio | 设置新生代、老年代的比例(默认1:2) |
-XX:SurvivorRatio | 设置 eden和 Survivor比例(默认8:1) |
-XX:MaxMetaspaceSize | 设置元空间最大大小 |
对于响应时间敏感应用,可以设置较大堆内存,减少GC次数
对于吞吐量优先应用,适当增大新生代,减少对象晋升的频率,减少Minor Gc次数
- 垃圾回收器设置
GC 类型 | 参数 | jdk版本 | 适用场景 | 特点 |
---|---|---|---|---|
SerialGc(串行GC) | -XX:+UseSerialGC | JDK 1.3+ | 单线程、低内存场景,例如简单桌面应用畅叙 | 单线程回收,新生代使用复制算法,老年代使用标记-压缩算法,停顿时间较长,不适合高并发场景 |
ParallelGc(并发GC) | -XX:+UseParallelGC | jdk 1.4+(jdk8及以前默认GC) | 高吞吐量应用 | 多线程回收,吞吐量优先;新生代使用复制算法,老年代使用标记-压缩算法 |
CMS GC | -XX:+UseConcMarkSweepGC | jdk 1.4+(jdk15之后已废弃) | 响应时间优先场景,例如在线交易、金融系统 | 并发标记清除,降低老年代回收停顿时间;存在碎片化问题,容易导致Full GC;JKD9之后被G1替代,JDK14后废弃 |
G1 GC | -XX:+UseG1GC | jdk 7+(jdk9之后作为默认GC) | 大内存(>4GB)、低延迟场景,如试试推荐、游戏服务等 | 面相区域分代收集,避免老年代停顿;支持用户指定最大停顿时间目标,平衡吞吐量和响应时间 |
ZGC | -XX:+UseZGC | jdk 11+ | 超低延迟场景,例如高频交易,实时系统 | 停顿时间<10ms,面向超大堆(支持TB级内存);使用染色指针技术避免全局暂停;与G1、CMS相比进一步降低停顿时间 |
Shenandoah GC | -XX:+UseShenandoahGC | jdk 12+ | 超低延迟场景,适合低延迟的业务系统,如实时数据处理 | 并发标记-压缩算法,减少老年代碎片化;与ZGC类似,提供更短的停顿时间,堆内存压缩性能更新,比ZGC更适合小型堆(<1TB) |
Epsilon GC | -XX:+UseEpsilonGC | jdk 11+ | 开发和测试场景,无垃圾回收需求,如临时程序或压力测试 | 无垃圾回收起,禁用垃圾回收仅分配内存,内存耗尽直接抛出oom异常 |
小型应用或单线程任务,使用Serial GC
高吞吐量要求,使用Parallel GC
低延迟应用,优先考虑G1 GC,如果延迟要求极低,选择ZGC或Shenandoah GC
测试或无需回收,使用Epsilon GC
- GC日志参数
参数 | 含义 |
---|---|
-Xlog:gc | 启用GC日志输出(jdk>8) |
-Xlog:gc*:file=gc.log | 将GC日志写入文件 |
-XX:+PrintGCDetails | 打印详细的GC日志(jdk<= 8) |
-XX:+PrintGCApplicationStoppedTime | 打印程序因GC停顿的时间 |
-XX:+PrintTenuringDistribution | 打印对象在新生代中的晋升年龄分布 |
- 线程栈大小
参数 | 含义 |
---|---|
-Xss | 设置线程栈大小 |
如果线程数较多,适当减少栈大小
如果线程需要深度递归,增大栈大小
- 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
诊断结果:
优化方案:
# 原JVM参数
-Xmx4g -Xms4g -XX:+UseParallelGC
# 优化后参数
-Xmx8g -Xms8g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:InitiatingHeapOccupancyPercent=45
-XX:G1ReservePercent=15
+ 添加本地缓存失效策略
优化效果:
指标 | 优化前 | 优化后 |
---|---|---|
Full GC频率 | 5分钟/次 | 无Full GC |
平均响应时间 | 350ms | 120ms |
吞吐量 | 1200TPS | 4500TPS |
大数据处理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);
}
优化方案:
增加批处理限制
List<DataRecord> buffer = new ArrayList<>(1000); while((record = readNext()) != null) { buffer.add(record); if(buffer.size() >= 1000) { process(buffer); buffer.clear(); } }
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]
调优步骤:
调整Region大小
-XX:G1HeapRegionSize=4m +XX:G1HeapRegionSize=8m
优化并行阶段
-XX:ConcGCThreads=4 +XX:ConcGCThreads=8 -XX:ParallelGCThreads=16 +XX:ParallelGCThreads=24
内存分配优化
-XX:+UseTLAB +XX:TLABSize=128k -XX:-ResizeTLAB +XX:+ResizeTLAB
效果对比:
参数 | 优化前停顿 | 优化后停顿 |
---|---|---|
Young GC | 230ms | 150ms |
Mixed GC | 450ms | 280ms |
吞吐量损失 | 12% | 6% |
内存泄漏排查案例
现象描述:
- 服务运行3天后内存占用达95%
- 重启后内存增长曲线异常
排查工具组合:
即时分析:
jcmd 38742 GC.class_histogram | grep -v "java.lang"
趋势监控:
jstat -gcutil 38742 60s
堆转储分析:
// 使用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"));
修复效果:
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 GC | G1 GC | ZGC |
---|---|---|---|
最大停顿时间 | 1200ms | 450ms | 0.8ms |
吞吐量损失 | 8% | 12% | 4% |
内存占用 | 18GB | 16GB | 20GB |
99.9%延迟 | 35ms | 25ms | 8ms |
容器环境调优实践
典型问题:
- Kubernetes Pod频繁OOMKilled
- 容器内存限制8GB,但实际使用超限
关键配置:
# 容器规范
resources:
limits:
memory: "8Gi"
cpu: "4"
# JVM参数优化
-XX:+UseContainerSupport
+XX:MaxRAMPercentage=75.0
+XX:ActiveProcessorCount=4
-XX:+AlwaysPreTouch
内存分布优化:
监控方案:
# 容器内存监控
kubectl top pod
# JVM内部监控
jcmd 1 VM.native_memory summary
优化效果:
- 内存使用稳定在7.5GB以内
- 无OOMKilled发生
GC频率降低40%
本文由博客群发一文多发等运营工具平台 OpenWrite 发布
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。