文章 601
评论 5
浏览 236341
JVM 调优不用背参数:记住这 3 个原则就够了

JVM 调优不用背参数:记住这 3 个原则就够了

一、引言

JVM 调优是 Java 开发者的必修课,但面对数百个 JVM 参数,很多人感到无从下手。

核心结论:JVM 调优不用背参数,记住 3 个原则就够了:

  1. 先监控再调优:不看 GC 日志就调参等于盲人摸象
  2. 代码优化优先:大多数性能问题是代码问题,不是参数问题
  3. 只调必要参数:堆大小、GC 算法、线程栈大小(极端场景)

二、原则 1:先监控再调优

2.1 不看 GC 日志就调参等于盲人摸象

错误做法:
┌─────────────────────────────────────────────────────┐
│                                                     │
│  服务器内存 8GB,直接设置 -Xmx6g -Xms6g               │
│  然后上线...                                         │
│                                                     │
│  结果:可能导致频繁 Full GC,性能反而下降              │
│                                                     │
└─────────────────────────────────────────────────────┘

正确做法:
┌─────────────────────────────────────────────────────┐
│                                                     │
│  1. 开启 GC 日志                                     │
│  2. 运行一段时间(至少 24 小时)                       │
│  3. 分析 GC 日志,了解堆使用情况                       │
│  4. 根据分析结果调整参数                              │
│                                                     │
└─────────────────────────────────────────────────────┘

2.2 开启 GC 日志

JDK 8 及之前

java -XX:+PrintGCDetails \
     -XX:+PrintGCTimeStamps \
     -XX:+PrintGCDateStamps \
     -Xloggc:/var/log/app/gc.log \
     -jar your-app.jar

JDK 9+(统一日志格式)

java -Xlog:gc*:file=/var/log/app/gc.log:time,uptime,level,tags \
     -Xlog:safepoint*:file=/var/log/app/safepoint.log \
     -jar your-app.jar

参数说明

参数说明
gc*输出所有 GC 相关日志
:file=xxx输出到文件
:time输出时间戳
:uptime输出 JVM 启动后的时间
:level输出日志级别
:tags输出日志标签

2.3 解读 GC 日志

JDK 8 GC 日志示例

2024-01-15T10:30:45.123+0800: [GC (Allocation Failure) [PSYoungGen: 1536M->256M(2048M)] 1536M->256M(6144M), 0.1234560 secs] [Times: user=0.34, sys=0.02, real=0.12 secs]

解读

字段说明
GC (Allocation Failure)GC 类型(Minor GC,分配失败触发)
PSYoungGen: 1536M->256M(2048M)年轻代使用量:1536M → 256M,总容量 2048M
1536M->256M(6144M)堆使用量:1536M → 256M,总容量 6144M
0.1234560 secsGC 耗时 123ms

JDK 9+ GC 日志示例

[10.304s][info][gc] GC(1) Pause Young (Allocation Failure) 1536M->256M(6144M) 123.456ms
[10.304s][info][gc] GC(1) Young Generation: 1536M->256M(2048M)
[10.304s][info][gc] GC(1) Survivor Space: 128M->0M(256M)
[10.304s][info][gc] GC(1) Old Generation: 0M->0M(4096M)

解读

字段说明
[10.304s]JVM 启动后 10.304 秒
[info]日志级别
[gc]日志标签
GC(1)第 1 次 GC
Pause Young年轻代暂停
1536M->256M(6144M)堆使用量变化
123.456msGC 耗时

2.4 关键指标

GC 日志关键指标:

┌─────────────────────────────────────────────────────┐
│                                                     │
│  1. Minor GC 频率                                    │
│     └── 理想:每 10-30 秒一次                         │
│                                                     │
│  2. Minor GC 耗时                                    │
│     └── 理想:< 50ms                                 │
│                                                     │
│  3. Full GC 频率                                    │
│     └── 理想:每小时 < 1 次                           │
│                                                     │
│  4. Full GC 耗时                                    │
│     └── 理想:< 500ms                                │
│                                                     │
│  5. 堆使用率                                        │
│     └── 理想:60%-70%                                │
│                                                     │
└─────────────────────────────────────────────────────┘

三、原则 2:代码优化优先

3.1 大多数性能问题是代码问题

性能问题分布:

┌─────────────────────────────────────────────────────┐
│                                                     │
│  代码问题:███████████████████████████░░░  80%       │
│  JVM 参数问题:██████████░░░░░░░░░░░░░░░░  20%       │
│                                                     │
└─────────────────────────────────────────────────────┘

常见代码问题:
1. 内存泄漏(未关闭的资源、静态集合)
2. 慢 SQL(未加索引、全表扫描)
3. 同步瓶颈(不必要的锁)
4. 大对象分配(频繁创建大数组、字符串拼接)

3.2 用 Arthas 找到慢方法

安装 Arthas

curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

使用 trace 命令

# 追踪指定方法及其调用链的耗时
trace com.example.service.OrderService createOrder

# 结果示例:
`---ts=2024-01-15 10:30:45.123;thread_name=http-nio-8080-exec-1;id=1;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@12345678
    `---[150.00ms] com.example.service.OrderService:createOrder()
        +---[10.00ms] com.example.repository.OrderRepository:save()
        +---[100.00ms] com.example.service.PaymentService:pay()
        |   `---[90.00ms] com.example.client.PaymentClient:callPaymentAPI()
        +---[30.00ms] com.example.service.NotificationService:send()
        +---[0.00ms] com.example.mapper.OrderMapper:toDTO()

解读

字段说明
[150.00ms]总耗时 150ms
[100.00ms]PaymentService.pay() 耗时 100ms(瓶颈)
[90.00ms]PaymentClient.callPaymentAPI() 耗时 90ms

使用 watch 命令

# 观察方法的入参、出参和异常
watch com.example.service.OrderService createOrder "{params, returnObj, throwExp}" -x 2

# 结果示例:
method=com.example.service.OrderService.createOrder location=AtExit
ts=2024-01-15 10:30:45.123
params[0]=OrderCreateRequest(userId=1001, productId=2001, quantity=2)
returnObj=OrderDTO(orderId=3001, amount=99.98, status=CREATED)
throwExp=null

使用 profiler 命令

# 启动 CPU 分析,持续 60 秒
profiler start --event cpu
sleep 60
profiler stop --format html --file /tmp/cpu-profile.html

# 结果:生成火焰图,直观看到 CPU 热点

3.3 代码优化示例

问题 1:频繁创建大对象

// 优化前
public String generateReport(List<Order> orders) {
    StringBuilder sb = new StringBuilder();
    for (Order order : orders) {
        // 每次循环创建新的 StringBuilder
        sb.append(new StringBuilder()
            .append("Order: ").append(order.getId())
            .append(", Amount: ").append(order.getAmount())
            .toString());
    }
    return sb.toString();
}

// 优化后
public String generateReport(List<Order> orders) {
    StringBuilder sb = new StringBuilder();
    for (Order order : orders) {
        // 复用同一个 StringBuilder
        sb.append("Order: ").append(order.getId())
          .append(", Amount: ").append(order.getAmount());
    }
    return sb.toString();
}

问题 2:未关闭的资源

// 优化前
public String readFile(String path) throws IOException {
    BufferedReader reader = new BufferedReader(new FileReader(path));
    String content = reader.readLine();
    // 忘记关闭 reader,可能导致资源泄漏
    return content;
}

// 优化后
public String readFile(String path) throws IOException {
    try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
        // try-with-resources 自动关闭资源
        return reader.readLine();
    }
}

四、原则 3:只调必要参数

4.1 参数调优优先级

参数调优优先级:

┌─────────────────────────────────────────────────────┐
│                                                     │
│  第一优先级:堆大小(-Xmx/-Xms)                      │
│  └── 决定了 GC 的频率和内存使用上限                    │
│                                                     │
│  第二优先级:GC 算法(-XX:+UseG1GC/-XX:+UseZGC)      │
│  └── 决定了 GC 的暂停时间和吞吐量                      │
│                                                     │
│  第三优先级:其他参数(线程栈、元空间等)               │
│  └── 一般不需要调,除非有特定问题                      │
│                                                     │
└─────────────────────────────────────────────────────┘

4.2 堆大小设置

基本公式

-Xmx = -Xms = 可用内存 * 0.7

注意事项

堆大小设置注意事项:

1. -Xmx 和 -Xms 设置为相同值,避免堆扩展开销
2. 不要超过物理内存的 80%,给系统留余量
3. 不要设置过大,否则 Full GC 耗时会很长
4. 根据 GC 日志调整,观察堆使用率是否在 60%-70%

示例

# 服务器内存 8GB,设置堆大小为 5GB
java -Xmx5g -Xms5g -jar your-app.jar

# 服务器内存 16GB,设置堆大小为 10GB
java -Xmx10g -Xms10g -jar your-app.jar

4.3 GC 算法选择

G1 GC(默认,JDK 9+)

适用场景:大多数企业级应用,堆大小 4GB-32GB

特点

  • 目标是控制 GC 暂停时间(默认 200ms)
  • 分区收集,逐步清理
  • 适合需要低延迟的场景

参数

java -Xmx5g -Xms5g \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:InitiatingHeapOccupancyPercent=70 \
     -jar your-app.jar
参数说明
-XX:+UseG1GC使用 G1 GC
-XX:MaxGCPauseMillis=200目标暂停时间 200ms
-XX:InitiatingHeapOccupancyPercent=70堆使用率达到 70% 时触发并发收集

ZGC(JDK 15+ 生产可用)

适用场景:大堆内存(32GB+),需要极低延迟

特点

  • 暂停时间极短(< 10ms)
  • 并发压缩
  • 适合堆内存较大的场景

参数

# JDK 15+
java -Xmx32g -Xms32g \
     -XX:+UseZGC \
     -jar your-app.jar

# JDK 21+(分代 ZGC)
java -Xmx32g -Xms32g \
     -XX:+UseZGC \
     -XX:+ZGenerational \
     -jar your-app.jar
参数说明
-XX:+UseZGC使用 ZGC
-XX:+ZGenerational使用分代 ZGC(JDK 21+)

GC 算法对比

特性G1 GCZGC
JDK 版本JDK 7+(默认 JDK 9+)JDK 15+(生产可用)
暂停时间~200ms< 10ms
堆大小4GB-32GB8GB-16TB
并发压缩
适用场景大多数企业应用大堆内存、低延迟要求

4.4 线程栈大小

默认值:1MB(JDK 8)

何时需要调整

需要调整线程栈大小的场景:

1. 深度递归调用(栈溢出)→ 增大 -Xss
2. 大量线程(内存不足)→ 减小 -Xss

注意:调整栈大小风险较高,需要谨慎

示例

# 增大栈大小(处理深度递归)
java -Xss2m -jar your-app.jar

# 减小栈大小(大量线程)
java -Xss256k -jar your-app.jar

五、各场景推荐参数速查表

5.1 小型服务(2GB 内存)

# 适合:单实例小型应用、开发环境
java -Xmx1536m -Xms1536m \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -Xlog:gc*:file=/var/log/app/gc.log:time,uptime \
     -jar your-app.jar

5.2 中型服务(8GB 内存)

# 适合:微服务、中等流量应用
java -Xmx5g -Xms5g \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:InitiatingHeapOccupancyPercent=70 \
     -XX:ParallelGCThreads=4 \
     -XX:ConcGCThreads=2 \
     -Xlog:gc*:file=/var/log/app/gc.log:time,uptime,level,tags \
     -jar your-app.jar

5.3 大型服务(16GB+ 内存,JDK 8/11)

# 适合:高并发应用、数据处理服务(JDK 8/11)
java -Xmx10g -Xms10g \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=300 \
     -XX:InitiatingHeapOccupancyPercent=70 \
     -XX:ParallelGCThreads=8 \
     -XX:ConcGCThreads=4 \
     -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/var/log/app/heapdump.hprof \
     -Xlog:gc*:file=/var/log/app/gc.log:time,uptime,level,tags \
     -jar your-app.jar

5.4 大型服务(32GB+ 内存,JDK 15+)

# 适合:超大堆内存应用、极低延迟要求(JDK 15+)
java -Xmx24g -Xms24g \
     -XX:+UseZGC \
     -XX:+ZGenerational \
     -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/var/log/app/heapdump.hprof \
     -Xlog:gc*:file=/var/log/app/gc.log:time,uptime,level,tags \
     -jar your-app.jar

5.5 大数据处理(128GB+ 内存)

# 适合:Spark、Flink 等大数据框架
java -Xmx96g -Xms96g \
     -XX:+UseZGC \
     -XX:+ZGenerational \
     -XX:ParallelGCThreads=16 \
     -XX:ConcGCThreads=8 \
     -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/var/log/app/heapdump.hprof \
     -Xlog:gc*:file=/var/log/app/gc.log:time,uptime,level,tags \
     -jar your-app.jar

5.6 速查表汇总

场景内存JDK 版本推荐参数
小型服务2GB任意-Xmx1536m -Xms1536m -XX:+UseG1GC
中型服务8GB任意-Xmx5g -Xms5g -XX:+UseG1GC -XX:MaxGCPauseMillis=200
大型服务16GB+8/11-Xmx10g -Xms10g -XX:+UseG1GC -XX:MaxGCPauseMillis=300
大型服务32GB+15+-Xmx24g -Xms24g -XX:+UseZGC -XX:+ZGenerational
大数据处理128GB+15+-Xmx96g -Xms96g -XX:+UseZGC -XX:+ZGenerational

六、总结

6.1 三个原则回顾

三个原则回顾:

┌─────────────────────────────────────────────────────┐
│                                                     │
│  原则 1:先监控再调优                                 │
│  ├── 开启 GC 日志                                    │
│  ├── 运行一段时间                                     │
│  └── 分析日志后再调整                                 │
│                                                     │
│  原则 2:代码优化优先                                 │
│  ├── 用 Arthas trace 找到慢方法                       │
│  ├── 用 profiler 分析 CPU 热点                        │
│  └── 修复代码问题比调参更有效                          │
│                                                     │
│  原则 3:只调必要参数                                 │
│  ├── 堆大小:-Xmx/-Xms                               │
│  ├── GC 算法:G1(默认)或 ZGC(大堆)                 │
│  └── 线程栈:极端场景才需要调                          │
│                                                     │
└─────────────────────────────────────────────────────┘

6.2 调优流程

调优流程:

┌─────────────────────────────────────────────────────┐
│                                                     │
│  1. 开启 GC 日志                                      │
│     ↓                                               │
│  2. 运行应用(至少 24 小时)                            │
│     ↓                                               │
│  3. 分析 GC 日志                                      │
│     ├── Minor GC 频率和耗时                            │
│     ├── Full GC 频率和耗时                            │
│     └── 堆使用率                                      │
│     ↓                                               │
│  4. 用 Arthas 分析代码性能                             │
│     ├── trace 找到慢方法                              │
│     ├── profiler 分析 CPU 热点                         │
│     └── watch 观察方法入参出参                         │
│     ↓                                               │
│  5. 优化代码                                          │
│     ↓                                               │
│  6. 调整 JVM 参数                                      │
│     ├── 堆大小                                        │
│     └── GC 算法                                       │
│     ↓                                               │
│  7. 持续监控,迭代优化                                  │
│                                                     │
└─────────────────────────────────────────────────────┘

6.3 常见误区

常见误区:

┌─────────────────────────────────────────────────────┐
│                                                     │
│  ❌ 误区 1:把堆设置得越大越好                          │
│     → 堆越大,Full GC 耗时越长                         │
│                                                     │
│  ❌ 误区 2:不看日志直接调参                            │
│     → 盲目调参可能适得其反                              │
│                                                     │
│  ❌ 误区 3:忽视代码问题                               │
│     → 80% 的性能问题是代码问题                          │
│                                                     │
│  ❌ 误区 4:追求最新的 GC 算法                         │
│     → ZGC 不是万能的,G1 对于大多数场景足够好             │
│                                                     │
└─────────────────────────────────────────────────────┘

💡 互动话题:你在项目中遇到过哪些 JVM 性能问题?是怎么解决的?欢迎在评论区分享!


标题:JVM 调优不用背参数:记住这 3 个原则就够了
作者:jiangyi
地址:http://jiangyi.space/articles/2026/07/24/1784449432238.html
公众号:服务端技术精选

服务端开发博客:后端架构、高并发、性能优化与微服务实战教程

取消