服务响应从500ms优化到50ms,JVM参数这样配
上周生产环境告警,接口响应时间飙到500ms,用户疯狂投诉。
排查发现是GC太频繁,Young GC每秒好几次。调了JVM参数后,响应时间降到50ms。把调优过程记录下来。
一、为什么要调优?
常见问题
- 频繁Full GC:服务卡顿
- OOM:服务挂掉
- GC时间过长:响应延迟高
调优目标
- 吞吐量:GC时间占比尽量小
- 延迟:GC停顿时间尽量短
- 内存占用:堆内存尽量小
不同场景侧重点不同。
二、JVM内存结构
┌─────────────────────────────────────┐
│ JVM Heap │
│ ┌─────────────┬─────────────────┐ │
│ │ Young Gen │ Old Gen │ │
│ │ ┌────┬────┐ │ │ │
│ │ │Eden│ S0 │ │ │ │
│ │ │ │ S1 │ │ │ │
│ │ └────┴────┘ │ │ │
│ └─────────────┴─────────────────┘ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Metaspace │
└─────────────────────────────────────┘
- **Young Gen**:新生代,对象先在这里分配
- **Old Gen**:老年代,存活久的对象晋升到这里
- **Metaspace**:元空间,存储类元数据(JDK8+)
---
## 三、垃圾收集器选择
### JDK 8
| 收集器 | 特点 | 适用场景 |
|-------|------|---------|
| Parallel GC | 吞吐量优先 | 后台批处理 |
| CMS | 低延迟 | Web应用(已废弃) |
| **G1** | 平衡吞吐和延迟 | 推荐 |
### JDK 11+
| 收集器 | 特点 | 适用场景 |
|-------|------|---------|
| **G1** | 默认收集器 | 通用场景 |
| ZGC | 超低延迟(<10ms) | 大内存、低延迟要求 |
| Shenandoah | 低延迟 | 类似ZGC |
### JDK 21+
| 收集器 | 特点 |
|-------|------|
| G1 | 持续优化,更稳定 |
| **ZGC** | 生产可用,推荐大内存场景 |
| 分代ZGC | JDK21新增,进一步优化 |
---
## 四、基础参数配置
### 堆内存
```bash
# 初始堆大小
-Xms4g
# 最大堆大小
-Xmx4g
# 建议:Xms = Xmx,避免动态调整开销
新生代
# 新生代大小(G1不建议设置)
-Xmn2g
# 新生代比例
-XX:NewRatio=2 # Old:Young = 2:1
元空间
# 元空间初始大小
-XX:MetaspaceSize=256m
# 元空间最大大小
-XX:MaxMetaspaceSize=512m
五、G1收集器配置
基本配置
-XX:+UseG1GC
-Xms4g
-Xmx4g
-XX:MaxGCPauseMillis=200
重要参数
# 目标停顿时间(毫秒)
-XX:MaxGCPauseMillis=200
# Region大小(1-32MB,2的幂)
-XX:G1HeapRegionSize=8m
# 触发Mixed GC的阈值
-XX:InitiatingHeapOccupancyPercent=45
# 并发GC线程数
-XX:ConcGCThreads=4
# 并行GC线程数
-XX:ParallelGCThreads=8
G1调优建议
- 不要设置新生代大小(-Xmn),让G1自己调整
- MaxGCPauseMillis不要设太小,否则GC太频繁
- 堆大小建议4G以上,G1在小堆上优势不明显
六、ZGC配置(JDK11+)
基本配置
-XX:+UseZGC
-Xms8g
-Xmx8g
JDK 21+分代ZGC
-XX:+UseZGC
-XX:+ZGenerational
-Xms8g
-Xmx8g
ZGC特点
- 停顿时间<10ms,与堆大小无关
- 支持TB级堆内存
- 适合大内存、低延迟场景
七、GC日志配置
JDK 8
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-Xloggc:/var/log/gc.log
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=20M
JDK 9+
-Xlog:gc*:file=/var/log/gc.log:time,uptime,level,tags:filecount=5,filesize=20m
GC日志分析工具
- GCEasy:在线分析 https://gceasy.io
- GCViewer:本地工具
- VisualVM:实时监控
八、OOM配置
# OOM时生成堆转储
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/heapdump.hprof
# OOM时执行脚本(可选)
-XX:OnOutOfMemoryError="kill -9 %p"
九、生产环境配置模板
4核8G服务器
java \
-Xms4g \
-Xmx4g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/heapdump.hprof \
-Xlog:gc*:file=/var/log/gc.log:time,uptime:filecount=5,filesize=20m \
-jar app.jar
8核16G服务器
java \
-Xms12g \
-Xmx12g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=100 \
-XX:G1HeapRegionSize=16m \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/heapdump.hprof \
-Xlog:gc*:file=/var/log/gc.log:time,uptime:filecount=5,filesize=20m \
-jar app.jar
大内存低延迟(JDK21+)
java \
-Xms32g \
-Xmx32g \
-XX:+UseZGC \
-XX:+ZGenerational \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/heapdump.hprof \
-Xlog:gc*:file=/var/log/gc.log:time,uptime:filecount=5,filesize=20m \
-jar app.jar
十、调优步骤
1. 收集数据
# 查看当前JVM参数
jinfo -flags <pid>
# 查看堆内存使用
jstat -gc <pid> 1000
# 查看GC情况
jstat -gcutil <pid> 1000
2. 分析GC日志
关注指标:
- GC频率:每分钟几次?
- GC耗时:平均多少ms?
- Full GC:有没有?频率?
- 堆内存:Old区使用率?
3. 调整参数
常见调整:
- 频繁Young GC → 增大新生代
- 频繁Full GC → 检查内存泄漏/增大堆
- GC时间长 → 换收集器/调整参数
4. 验证效果
对比调整前后的GC指标。
十一、监控指标
关键指标
| 指标 | 健康值 | 说明 |
|---|---|---|
| Young GC频率 | <10次/分钟 | 太频繁说明新生代小 |
| Young GC耗时 | <50ms | 太长说明新生代大 |
| Full GC频率 | <1次/小时 | 频繁要排查 |
| Full GC耗时 | <1秒 | 太长影响服务 |
| 堆使用率 | <70% | 太高容易OOM |
Prometheus监控
# JMX Exporter配置
rules:
- pattern: 'java.lang<type=GarbageCollector, name=(.*)><>CollectionCount'
name: jvm_gc_collection_count
labels:
gc: "$1"
- pattern: 'java.lang<type=GarbageCollector, name=(.*)><>CollectionTime'
name: jvm_gc_collection_time_ms
labels:
gc: "$1"
十二、远程JVM监控
生产环境的JVM,怎么在本地监控?
我用星空组网工具把本地和服务器连起来,然后用VisualVM远程连接:
# 服务器启动时加JMX参数
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9010
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Djava.rmi.server.hostname=192.168.188.10
本地VisualVM直接连 192.168.188.10:9010,实时看堆内存、GC情况、线程状态。
比登服务器跑jstat方便多了。
总结
JVM调优核心:
| 步骤 | 内容 |
|---|---|
| 1. 选收集器 | JDK8用G1,JDK21+可用ZGC |
| 2. 设置堆大小 | Xms=Xmx,建议物理内存的60-70% |
| 3. 开启GC日志 | 必须开,出问题能分析 |
| 4. 配置OOM dump | 必须配,OOM时能排查 |
| 5. 监控指标 | GC频率、耗时、堆使用率 |
记住:先监控后调优,数据说话。
有问题评论区交流~

浙公网安备 33010602011771号