JVM调优指南
JVM调优是Java性能优化的核心技能,目标是减少GC暂停、提高吞吐量、避免内存溢出。
一、调优前准备
1. 明确目标
吞吐量优先:后台批处理、大数据计算
低延迟优先:互联网应用、实时系统
内存占用优先:资源受限环境
2. 监控工具
# 命令行工具 jps # 查看Java进程 jstat # 监控GC情况 jmap # 内存快照 jstack # 线程快照 jinfo # 查看JVM参数 # 可视化工具 jconsole # JMX监控 jvisualvm # 综合监控 JMC # Java Mission Control Arthas # 阿里开源诊断工具3. JVM运行情况预估
年轻代对象增长速率(每秒多少对象进入Eden区)
Young GC触发频率和每次耗时
Young GC后多少对象存活和进入老年代
Full GC触发频率和每次耗时
二、核心参数详解
1. 内存设置
# 堆内存 -Xms4g # 初始堆大小 -Xmx4g # 最大堆大小 -Xmn2g # 年轻代大小 -XX:NewRatio=2 # 老年代/年轻代比例(默认2) -XX:SurvivorRatio=8 # Eden/Survivor比例(默认8) # 元空间(JDK8+) -XX:MetaspaceSize=256m # 初始元空间 -XX:MaxMetaspaceSize=256m # 最大元空间 # 直接内存 -XX:MaxDirectMemorySize=1g # NIO直接内存2. GC调优参数
# Parallel GC -XX:ParallelGCThreads=4 # GC线程数 -XX:MaxGCPauseMillis=200 # 目标最大GC暂停时间 -XX:GCTimeRatio=99 # 吞吐量目标(99%应用时间) # CMS -XX:CMSInitiatingOccupancyFraction=70 # CMS触发阈值 -XX:+UseCMSInitiatingOccupancyOnly # 使用指定阈值 # G1 GC -XX:MaxGCPauseMillis=200 # 期望暂停时间 -XX:G1HeapRegionSize=16m # Region大小(1-32M) -XX:InitiatingHeapOccupancyPercent=45 # 触发并发GC的堆占用率 -XX:G1NewSizePercent=5 # 年轻代最小比例 -XX:G1MaxNewSizePercent=60 # 年轻代最大比例三、GC日志分析
1. 启用GC日志
# JDK8 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Xloggc:gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M # JDK9+ -Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=10,filesize=100M2. 日志解读
# Parallel GC日志示例 [GC (Allocation Failure) [PSYoungGen: 51200K->7168K(59392K)] 51200K->15120K(196608K), 0.0123456 secs] # 解读: # - 年轻代:51200K -> 7168K,总大小59392K # - 堆总:51200K -> 15120K,总大小196608K # - 耗时:0.0123456秒 # G1 GC日志 [GC pause (G1 Evacuation Pause) (young), 0.0152345 secs] [Parallel Time: 12.3 ms, GC Workers: 8] ... [Eden: 512M(512M)->0B(512M) Survivors: 64M->64M Heap: 800M(2048M)->450M(2048M)]四、常见问题场景调优
场景1:频繁Full GC
# 问题:系统响应慢,CPU飙升 # 排查步骤: jstat -gcutil <pid> 1000 # 查看GC频率 # 解决方案: # 1. 增大堆内存 -Xms4g -Xmx4g # 2. 调整对象晋升阈值 -XX:MaxTenuringThreshold=15 -XX:TargetSurvivorRatio=90 # 3. 使用G1 GC -XX:+UseG1GC -XX:MaxGCPauseMillis=100场景2:内存泄漏
# 生成堆转储 jmap -dump:live,format=b,file=heap.hprof <pid> # 自动触发OOM时dump -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump # 分析工具:Eclipse MAT、JProfiler、VisualVM场景3:GC暂停时间过长
# 目标:从200ms降到50ms # G1调优方案 -XX:+UseG1GC -XX:MaxGCPauseMillis=50 # 降低目标暂停时间 -XX:G1NewSizePercent=10 # 增大年轻代 -XX:G1HeapRegionSize=8m # 调整Region大小 -XX:ConcGCThreads=4 # 增加并发GC线程场景4:动态年龄判断机制导致Full GC频繁
# jstat -gcutil观察到的现象 S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 98.22 45.33 85.60 92.33 89.21 125 2.345 89 5.678 8.023 # 特点:YGC次数少,FGC次数异常多(几乎每次YGC后都伴随FGC) # 老年代占用85%,但实际存活对象并不多根本原因
JVM的动态年龄判断机制:当Survivor区中相同年龄的所有对象大小总和 > Survivor区的一半时,年龄大于等于该年龄的对象直接晋升到老年代。
解决方案
调整Survivor区大小
调整动态年龄阈值
增大年轻代空间
场景5:老年代空间担保机制导致Full GC > Minor GC
# jstat -gcutil观察到的异常现象 S0 S1 E O M YGC YGCT FGC FGCT GCT 0.00 5.22 92.33 45.60 78.33 15 0.345 89 5.678 6.023 # 关键指标: # YGC=15次,FGC=89次 → FGC远超YGC(正常情况下YGC应该远多于FGC) # Eden区92% → 频繁触发YGC # 老年代仅45% → 老年代空间充足但仍然FGC # GC日志特征 [GC (Allocation Failure) [PSYoungGen: 51200K->0K(59392K)] 51200K->50200K(196608K), 0.1234567 secs] [Full GC (Ergonomics) [PSYoungGen: 0K->0K(59392K)] [ParOldGen: 50200K->45000K(137216K)] 50200K->45000K(196608K), 0.4567890 secs] # 注意:每次YGC后立即跟随FGC根本原因
老年代空间担保机制:在YGC前,JVM检查老年代最大连续空间是否大于年轻代所有对象总空间。如果不足,为避免YGC失败,直接触发Full GC。
解决方案:增大老年代空间
# 原配置(问题配置) -Xms4g -Xmx4g -Xmn2g # 老年代2G -XX:+UseParallelGC # 优化方案A:整体增大堆内存 -Xms8g -Xmx8g -Xmn2g # 老年代从2G增加到6G(增加3倍) -XX:+UseParallelGC # 优化方案B:调整年轻代比例(不增加总堆,减少年轻代) -Xms4g -Xmx4g -Xmn1g # 年轻代从2G减少到1G,老年代从2G增加到3G -XX:NewRatio=3 # 老年代:年轻代=3:1(老年代3G,年轻代1G) -XX:+UseParallelGC