← 返回首页

JVM性能调优实战

📂 java ⏱ 2 min 339 words

JVM性能调优实战

调优目标

监控工具

jps - 查看Java进程

# 列出所有Java进程
jps -l

# 输出示例
12345 com.example.Application
67890 org.apache.catalina.startup.Bootstrap

jstat - GC统计信息

# 每1000ms输出一次GC统计,共输出10次
jstat -gcutil 12345 1000 10

# 输出示例
# S0     S1     E      O      M     CCS    YGC  YGCT   FGC  FGCT   GCT
# 0.00  45.23  67.89  32.14  95.67  92.34   25   0.456   3   1.234  1.690

jmap - 内存映射

# 堆内存概览
jmap -heap 12345

# 导出堆转储文件
jmap -dump:format=b,file=heap.dump 12345

# 触发GC
jmap -histo:live 12345 | head -20

jstack - 线程转储

# 导出线程栈
jstack 12345 > thread.dump

# 查看死锁
jstack -l 12345

VisualVM

# 启动VisualVM
jvisualvm

# 或使用JDK Mission Control
jmc

堆内存调优

设置合理的堆大小

# 初始堆和最大堆设置相同,避免动态扩展
java -Xms4g -Xmx4g -jar app.jar

# 查看默认堆大小
java -XX:+PrintFlagsFinal -version | grep HeapSize

新生代与老年代比例

# 默认比例
-XX:NewRatio=2    # 老年代:新生代 = 2:1

# 指定新生代大小
-XX:NewSize=1g     # 新生代初始大小
-XX:MaxNewSize=1g  # 新生代最大大小

# Eden与Survivor比例
-XX:SurvivorRatio=8  # Eden:S0:S1 = 8:1:1

调优示例

public class HeapTuningDemo {
    // 问题:频繁Full GC
    // 解决:增大老年代空间
    // -Xmx8g -XX:NewRatio=3

    // 问题:Minor GC太频繁
    // 解决:增大新生代空间
    // -Xmx4g -XX:NewSize=2g

    // 问题:大对象直接进老年代导致Full GC
    // 解决:调整大对象阈值
    // -XX:PretenureSizeThreshold=3145728 (3MB)
}

GC调优策略

选择合适的收集器

# 低延迟应用
java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar app.jar

# 高吞吐量应用
java -XX:+UseParallelGC -XX:ParallelGCThreads=8 -jar app.jar

# 超低延迟(JDK11+)
java -XX:+UseZGC -jar app.jar

GC日志分析

# Java 11+
java -Xlog:gc*:file=gc.log:time,uptime,level,tags -jar app.jar

# Java 8
java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -jar app.jar

G1调优参数

# G1调优
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200      # 目标停顿时间
-XX:G1HeapRegionSize=16m      # Region大小
-XX:InitiatingHeapOccupancyPercent=45  # 触发GC的堆占用比
-XX:G1ReservePercent=10       # 保留空间

内存泄漏排查

常见内存泄漏场景

// 1. 静态集合持有对象引用
public class MemoryLeak {
    private static List<byte[]> cache = new ArrayList<>();
    public void add(byte[] data) {
        cache.add(data); // 对象无法被GC
    }
}

// 2. 未关闭的资源
public void leak() {
    FileInputStream fis = new FileInputStream("file.txt");
    // 忘记关闭,资源泄漏
}

// 3. ThreadLocal使用后未清除
public void threadLocalLeak() {
    ThreadLocal<byte[]> local = new ThreadLocal<>();
    local.set(new byte[1024 * 1024]);
    // 线程池场景下ThreadLocal不会自动清理
}

排查步骤

// 1. 使用jmap导出堆转储
// jmap -dump:format=b,file=heap.dump 12345

// 2. 使用MAT分析
// 打开heap.dump,查看Dominator Tree

// 3. 查看占用内存最多的对象
// 4. 分析GC Roots引用链
// 5. 找到泄漏点并修复

线程调优

# 线程栈大小
-Xss256k    # 默认1MB,可减小到256k

# 查看线程数
jstack 12345 | grep -c "nid="
// 线程池调优示例
ExecutorService executor = new ThreadPoolExecutor(
    4,                          // 核心线程数
    8,                          // 最大线程数
    60L, TimeUnit.SECONDS,      // 空闲线程存活时间
    new LinkedBlockingQueue<>(1000) // 任务队列
);

常见问题与解决方案

问题 症状 解决方案
频繁Minor GC E区满,GC时间短 增大新生代空间
频繁Full GC O区满,GC时间长 增大堆空间,优化代码
OOM OutOfMemoryError 检查内存泄漏,增大堆空间
GC停顿长 响应时间波动大 使用G1或ZGC
CPU飙高 线程死循环 jstack分析线程状态

总结

JVM调优是一个持续的过程,需要结合监控数据和业务特点进行。优先解决内存泄漏和代码问题,再考虑JVM参数调优。