JVM调优实战——从Full GC到零停顿的优化之路问题来了系统开始咳嗽。不是宕机不是报错是每隔几小时就卡一下。前端响应从200ms跳到2000ms持续30秒后恢复。监控显示Full GC。症状分析1. 业务影响查询响应时间波动200ms → 2000ms → 200ms交易成功率下降99.9% → 99.5%用户投诉“系统一会儿快一会儿慢”2. 技术指标JVM堆内存8GYoung GC频率每分钟2-3次Full GC频率每3-4小时1次Full GC时长20-30秒3. 业务场景日均交易50万笔并发用户1000数据量人3000万历史记录100亿业务特点早9点、下午2点高峰月末结算峰值诊断过程第一步收集证据# 1. 查看GC原因jstat-gccausepid100010# 输出示例S0 S1 E O M CCS YGC YGCT FGC FGCT GCT LGCC GCC0.0096.8818.2372.4595.1292.3115632345.123451125.4561470.579Allocation Failure System.gc()关键信息LGCC上次GC原因Allocation Failure分配失败GCC当前GC原因System.gc()有人调了System.gc()FGC45次Full GCFGCT1125.456秒累计Full GC时间第二步分析堆内存# 2. 查看堆内存分布jmap-heappid# 输出关键信息Heap Configuration: MinHeapFreeRatio40MaxHeapFreeRatio70MaxHeapSize8589934592(8192.0MB)NewSize268435456(256.0MB)MaxNewSize2863661056(2731.0MB)OldSize5439488(5.1875MB)NewRatio2SurvivorRatio8Heap Usage: PS Young Generation Eden Space: capacity2147483648(2048.0MB)used1234567890(1177.6MB)free912919758(870.4MB)57.5% used From Space: capacity268435456(256.0MB)used0(0.0MB)free268435456(256.0MB)0.0% used To Space: capacity268435456(256.0MB)used0(0.0MB)free268435456(256.0MB)0.0% used PS Old Generation capacity6442450944(6144.0MB)used4567890123(4356.8MB)free1874560821(1787.2MB)70.9% used发现问题Old区使用率70.9%接近触发Full GC的阈值Young区Eden 57.5%Survivor区0%——对象直接进入Old区NewRatio2Young:Old1:2Young区偏小第三步查看GC日志# 3. 开启GC日志java-XX:PrintGCDetails-XX:PrintGCDateStamps-XX:PrintGCTimeStamps-Xloggc:gc.log...GC日志分析34567.890: [GC (Allocation Failure) [PSYoungGen: 1177M-256M(2048M)] 4356M-3456M(8192M), 0.456 secs] 45678.901: [Full GC (System.gc()) [PSYoungGen: 256M-0M(2048M)] [ParOldGen: 3200M-2987M(6144M)] 3456M-2987M(8192M), 23.456 secs]关键点Young GC后存活对象256M全部进入Old区Full GC由System.gc()触发不是内存不足Full GC耗时23.456秒停顿时间太长根本原因1. 代码问题有人调了System.gc()// 罪魁祸首某个第三方库publicclassBadLibrary{publicvoidcleanup(){// 错误手动触发Full GCSystem.gc();}}2. 配置问题Young区太小NewRatio2Young:Old1:2实际业务大量短期对象应该给更大Young区Survivor区使用率0%说明对象年龄增长过快3. 业务问题大对象直接进入Old区业务特点查询结果集大人列表报表数据缓存批量处理中间结果解决方案第一步禁止System.gc()# JVM参数-XX:DisableExplicitGC效果禁止代码中的System.gc()调用Full GC频率立即下降。第二步调整堆内存比例# 调整前-Xmx8g-Xms8g-XX:NewRatio2# 调整后-Xmx8g-Xms8g-XX:NewRatio1-XX:SurvivorRatio6参数解释NewRatio1Young区:Old区1:1各4GSurvivorRatio6Eden:Survivor6:1:1Eden 3G每个Survivor 0.5G第三步优化GC策略# 使用G1GC替代ParallelGC-XX:UseG1GC-XX:MaxGCPauseMillis200-XX:G1HeapRegionSize4m-XX:InitiatingHeapOccupancyPercent45G1GC优势可预测的停顿时间设置200ms目标并发标记减少STW时间自动区域划分避免内存碎片第四步监控大对象# 打印大对象分配堆栈-XX:PrintTenuringDistribution-XX:PrintAdaptiveSizePolicy-XX:HeapDumpOnOutOfMemoryError-XX:HeapDumpPath/tmp/heapdump.hprof验证效果调整前 vs 调整后指标调整前调整后Young GC频率2-3次/分钟3-4次/分钟Full GC频率每3-4小时1次0次2周内Full GC时长20-30秒0秒平均响应时间200ms波动150ms稳定99分位响应时间2000ms500msGC日志对比调整前[Full GC (System.gc()) 23.456s]调整后[GC pause (G1 Evacuation Pause) (young) 156ms] [GC pause (G1 Humongous Allocation) (young) 45ms]政务系统JVM调优要点1. 业务特点决定配置社保系统查询多结果集缓存 → 需要更大Old区批量处理临时对象多 → 需要更大Young区7×24小时不能重启 → 需要稳定GC策略2. 监控指标# 日常监控脚本#!/bin/bashPID$(jps|grepBootstrap|awk{print $1})echo JVM监控$(date)echo1. GC统计:jstat-gcutil$PID10005echo2. 堆内存:jmap-heap$PID|grep-A20Heap Usageecho3. 线程数:jstack$PID|grepjava.lang.Thread.State|wc-lecho4. 大对象:jmap-histo:live$PID|head-203. 参数模板# 社保系统JVM参数模板JDK8-Xmx8g-Xms8g-XX:UseG1GC-XX:MaxGCPauseMillis200-XX:G1HeapRegionSize4m-XX:InitiatingHeapOccupancyPercent45-XX:DisableExplicitGC-XX:PrintGCDetails-XX:PrintGCDateStamps-XX:PrintTenuringDistribution-Xloggc:/logs/gc.log-XX:UseGCLogFileRotation-XX:NumberOfGCLogFiles10-XX:GCLogFileSize10M-XX:HeapDumpOnOutOfMemoryError-XX:HeapDumpPath/tmp/heapdump.hprof4. 调优流程监控收集GC日志、堆内存、线程栈分析确定瓶颈Young/Old区、GC算法、代码问题调整修改参数小步快跑验证压测验证监控效果固化生产部署持续监控经验教训1. Full GC不一定是内存问题System.gc()调用JNI内存泄漏堆外内存溢出2. 不要迷信默认参数JDK默认参数针对通用场景政务系统有特殊性数据量大并发高稳定性要求高3. 调优是持续过程业务变化需要调整参数用户量增长数据量增加功能迭代4. 监控比调优更重要建立基线正常状态设置告警异常指标定期巡检主动发现技术细节1. 为什么G1GC适合政务系统可预测停顿设置最大停顿时间保证业务响应并发标记减少STW时间业务影响小区域划分避免内存碎片长期运行稳定2. 如何确定堆大小# 经验公式总内存操作系统预留(2G) 堆内存 堆外内存 其他进程# 社保系统示例16G服务器 - 操作系统2G - JVM堆8G - 堆外内存2GNetty、DirectBuffer - 数据库连接池1G - 其他3G缓冲3. Young区大小计算# 根据业务特点短期对象多Young区占比大60%-70% 长期对象多Old区占比大60%-70%# 社保系统查询结果缓存长期 临时计算短期# 采用平衡策略Young:Old 1:1总结1. 调优目标不是零GC而是业务无感知GC停顿时间 业务容忍时间社保200msGC频率 业务波动频率内存使用率 安全阈值80%2. 政务系统特殊性不能随便重启参数调整要谨慎数据不能丢GC不能导致数据不一致业务不能停停顿时间必须可控3. 从这次调优学到的先诊断后开药jstat、jmap、GC日志分析小步快跑每次只调整一个参数观察效果监控验证调整后必须验证数据说话持续优化业务变化参数也要变化full gc没有了这句话看似简单背后是分析、调整、无数次的监控验证。技术问题好解决难的是在业务不能停的前提下解决问题。
JVM调优实战——从Full GC到零停顿的优化之路
发布时间:2026/5/19 5:58:24
JVM调优实战——从Full GC到零停顿的优化之路问题来了系统开始咳嗽。不是宕机不是报错是每隔几小时就卡一下。前端响应从200ms跳到2000ms持续30秒后恢复。监控显示Full GC。症状分析1. 业务影响查询响应时间波动200ms → 2000ms → 200ms交易成功率下降99.9% → 99.5%用户投诉“系统一会儿快一会儿慢”2. 技术指标JVM堆内存8GYoung GC频率每分钟2-3次Full GC频率每3-4小时1次Full GC时长20-30秒3. 业务场景日均交易50万笔并发用户1000数据量人3000万历史记录100亿业务特点早9点、下午2点高峰月末结算峰值诊断过程第一步收集证据# 1. 查看GC原因jstat-gccausepid100010# 输出示例S0 S1 E O M CCS YGC YGCT FGC FGCT GCT LGCC GCC0.0096.8818.2372.4595.1292.3115632345.123451125.4561470.579Allocation Failure System.gc()关键信息LGCC上次GC原因Allocation Failure分配失败GCC当前GC原因System.gc()有人调了System.gc()FGC45次Full GCFGCT1125.456秒累计Full GC时间第二步分析堆内存# 2. 查看堆内存分布jmap-heappid# 输出关键信息Heap Configuration: MinHeapFreeRatio40MaxHeapFreeRatio70MaxHeapSize8589934592(8192.0MB)NewSize268435456(256.0MB)MaxNewSize2863661056(2731.0MB)OldSize5439488(5.1875MB)NewRatio2SurvivorRatio8Heap Usage: PS Young Generation Eden Space: capacity2147483648(2048.0MB)used1234567890(1177.6MB)free912919758(870.4MB)57.5% used From Space: capacity268435456(256.0MB)used0(0.0MB)free268435456(256.0MB)0.0% used To Space: capacity268435456(256.0MB)used0(0.0MB)free268435456(256.0MB)0.0% used PS Old Generation capacity6442450944(6144.0MB)used4567890123(4356.8MB)free1874560821(1787.2MB)70.9% used发现问题Old区使用率70.9%接近触发Full GC的阈值Young区Eden 57.5%Survivor区0%——对象直接进入Old区NewRatio2Young:Old1:2Young区偏小第三步查看GC日志# 3. 开启GC日志java-XX:PrintGCDetails-XX:PrintGCDateStamps-XX:PrintGCTimeStamps-Xloggc:gc.log...GC日志分析34567.890: [GC (Allocation Failure) [PSYoungGen: 1177M-256M(2048M)] 4356M-3456M(8192M), 0.456 secs] 45678.901: [Full GC (System.gc()) [PSYoungGen: 256M-0M(2048M)] [ParOldGen: 3200M-2987M(6144M)] 3456M-2987M(8192M), 23.456 secs]关键点Young GC后存活对象256M全部进入Old区Full GC由System.gc()触发不是内存不足Full GC耗时23.456秒停顿时间太长根本原因1. 代码问题有人调了System.gc()// 罪魁祸首某个第三方库publicclassBadLibrary{publicvoidcleanup(){// 错误手动触发Full GCSystem.gc();}}2. 配置问题Young区太小NewRatio2Young:Old1:2实际业务大量短期对象应该给更大Young区Survivor区使用率0%说明对象年龄增长过快3. 业务问题大对象直接进入Old区业务特点查询结果集大人列表报表数据缓存批量处理中间结果解决方案第一步禁止System.gc()# JVM参数-XX:DisableExplicitGC效果禁止代码中的System.gc()调用Full GC频率立即下降。第二步调整堆内存比例# 调整前-Xmx8g-Xms8g-XX:NewRatio2# 调整后-Xmx8g-Xms8g-XX:NewRatio1-XX:SurvivorRatio6参数解释NewRatio1Young区:Old区1:1各4GSurvivorRatio6Eden:Survivor6:1:1Eden 3G每个Survivor 0.5G第三步优化GC策略# 使用G1GC替代ParallelGC-XX:UseG1GC-XX:MaxGCPauseMillis200-XX:G1HeapRegionSize4m-XX:InitiatingHeapOccupancyPercent45G1GC优势可预测的停顿时间设置200ms目标并发标记减少STW时间自动区域划分避免内存碎片第四步监控大对象# 打印大对象分配堆栈-XX:PrintTenuringDistribution-XX:PrintAdaptiveSizePolicy-XX:HeapDumpOnOutOfMemoryError-XX:HeapDumpPath/tmp/heapdump.hprof验证效果调整前 vs 调整后指标调整前调整后Young GC频率2-3次/分钟3-4次/分钟Full GC频率每3-4小时1次0次2周内Full GC时长20-30秒0秒平均响应时间200ms波动150ms稳定99分位响应时间2000ms500msGC日志对比调整前[Full GC (System.gc()) 23.456s]调整后[GC pause (G1 Evacuation Pause) (young) 156ms] [GC pause (G1 Humongous Allocation) (young) 45ms]政务系统JVM调优要点1. 业务特点决定配置社保系统查询多结果集缓存 → 需要更大Old区批量处理临时对象多 → 需要更大Young区7×24小时不能重启 → 需要稳定GC策略2. 监控指标# 日常监控脚本#!/bin/bashPID$(jps|grepBootstrap|awk{print $1})echo JVM监控$(date)echo1. GC统计:jstat-gcutil$PID10005echo2. 堆内存:jmap-heap$PID|grep-A20Heap Usageecho3. 线程数:jstack$PID|grepjava.lang.Thread.State|wc-lecho4. 大对象:jmap-histo:live$PID|head-203. 参数模板# 社保系统JVM参数模板JDK8-Xmx8g-Xms8g-XX:UseG1GC-XX:MaxGCPauseMillis200-XX:G1HeapRegionSize4m-XX:InitiatingHeapOccupancyPercent45-XX:DisableExplicitGC-XX:PrintGCDetails-XX:PrintGCDateStamps-XX:PrintTenuringDistribution-Xloggc:/logs/gc.log-XX:UseGCLogFileRotation-XX:NumberOfGCLogFiles10-XX:GCLogFileSize10M-XX:HeapDumpOnOutOfMemoryError-XX:HeapDumpPath/tmp/heapdump.hprof4. 调优流程监控收集GC日志、堆内存、线程栈分析确定瓶颈Young/Old区、GC算法、代码问题调整修改参数小步快跑验证压测验证监控效果固化生产部署持续监控经验教训1. Full GC不一定是内存问题System.gc()调用JNI内存泄漏堆外内存溢出2. 不要迷信默认参数JDK默认参数针对通用场景政务系统有特殊性数据量大并发高稳定性要求高3. 调优是持续过程业务变化需要调整参数用户量增长数据量增加功能迭代4. 监控比调优更重要建立基线正常状态设置告警异常指标定期巡检主动发现技术细节1. 为什么G1GC适合政务系统可预测停顿设置最大停顿时间保证业务响应并发标记减少STW时间业务影响小区域划分避免内存碎片长期运行稳定2. 如何确定堆大小# 经验公式总内存操作系统预留(2G) 堆内存 堆外内存 其他进程# 社保系统示例16G服务器 - 操作系统2G - JVM堆8G - 堆外内存2GNetty、DirectBuffer - 数据库连接池1G - 其他3G缓冲3. Young区大小计算# 根据业务特点短期对象多Young区占比大60%-70% 长期对象多Old区占比大60%-70%# 社保系统查询结果缓存长期 临时计算短期# 采用平衡策略Young:Old 1:1总结1. 调优目标不是零GC而是业务无感知GC停顿时间 业务容忍时间社保200msGC频率 业务波动频率内存使用率 安全阈值80%2. 政务系统特殊性不能随便重启参数调整要谨慎数据不能丢GC不能导致数据不一致业务不能停停顿时间必须可控3. 从这次调优学到的先诊断后开药jstat、jmap、GC日志分析小步快跑每次只调整一个参数观察效果监控验证调整后必须验证数据说话持续优化业务变化参数也要变化full gc没有了这句话看似简单背后是分析、调整、无数次的监控验证。技术问题好解决难的是在业务不能停的前提下解决问题。