服务响应从500ms优化到50ms,JVM参数这样配

上周生产环境告警,接口响应时间飙到500ms,用户疯狂投诉。

排查发现是GC太频繁,Young GC每秒好几次。调了JVM参数后,响应时间降到50ms。把调优过程记录下来。


一、为什么要调优?

常见问题

  1. 频繁Full GC:服务卡顿
  2. OOM:服务挂掉
  3. 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调优建议

  1. 不要设置新生代大小(-Xmn),让G1自己调整
  2. MaxGCPauseMillis不要设太小,否则GC太频繁
  3. 堆大小建议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频率、耗时、堆使用率

记住:先监控后调优,数据说话。

有问题评论区交流~


posted @ 2025-12-08 11:38  花宝宝  阅读(19)  评论(0)    收藏  举报