JVM性能调优:从定位问题到解决——线上CPU 100%怎么办?
2026/4/26 19:05:43 网站建设 项目流程
上回说到 并发锁,有个小伙伴问:”老师,生产环境CPU 100%,接口响应超时,该如何排查?”这让我想起了小王的一次线上事故——大促期间,服务CPU飙到100%,接口响应时间从500ms飙升到30s。今天我们就来聊聊 JVM性能调优,从问题定位到解决方案。

一、问题现场还原

那是一个大促的晚上,监控告警:

【紧急】订单服务CPU 100% 【紧急】接口响应时间 P99 > 30000ms 【紧急】Full GC频率:1次/分钟

小王赶紧登录服务器,执行top命令:

PID USER PR NI VIRT RES %CPU %MEM TIME+ COMMAND 12345 appuser 20 0 3.2g 1.8g 100 45.2 2:30.52 java

CPU 100%!这是什么情况?

二、问题排查步骤

2.1 第一步:找到占用CPU最高的线程

# 1. 查看Java进程PID jps # 输出:12345 OrderService # 2. 查看占用CPU最高的线程(-H显示线程,-p指定PID,-n显示3次,-d间隔1秒) top -H -p 12345 -n 3 -d 1 # 输出: PID USER PR NI VIRT RES %CPU %MEM TIME+ COMMAND 12348 appuser 20 0 3.2g 1.8g 89.5 45.2 1:25.30 java 12349 appuser 20 0 3.2g 1.8g 10.5 45.2 0:15.20 java 12350 appuser 20 0 3.2g 1.8g 0.0 45.2 0:00.00 java # 12348线程占用89.5%的CPU!

2.2 第二步:将线程PID转换为16进制

# 将12348转换为16进制 printf "%x\n" 12348 # 输出:303c

2.3 第三步:查看线程堆栈

# 查看线程堆栈(-l打印锁信息) jstack 12345 | grep 303c -A 20 # 输出: "pool-1-thread-1" #21 prio=5 os_prio=0 tid=0x00007f8c5c012800 nid=0x303c runnable [0x00007f8c52016000] java.lang.Thread.State: RUNNABLE at java.util.HashMap$TreeNode.transfer(HashMap.java:2015) at java.util.HashMap.resize(HashMap.java:703) at java.util.HashMap.putVal(HashMap.java:662) at java.util.HashMap.put(HashMap.java:611) at com.example.service.OrderService.processOrder(OrderService.java:125) at com.example.controller.OrderController.createOrder(OrderController.java:45)

发现问题:线程卡在HashMap.resize()上!

2.4 第四步:分析问题

// 问题代码 public class OrderService { private Map<Long, Order> orderCache = new HashMap<>(); public void processOrder(Order order) { // 大量并发调用put,导致HashMap扩容 orderCache.put(order.getId(), order); } }

问题原因

  • HashMap不是线程安全的
  • 多线程并发put会导致死循环(JDK 1.7)
  • 或者导致CPU 100%(JDK 1.8,扩容导致)

三、解决方案

3.1 方案一:使用ConcurrentHashMap(推荐)

public class OrderService { // 使用ConcurrentHashMap替代HashMap private Map<Long, Order> orderCache = new ConcurrentHashMap<>(); public void processOrder(Order order) { orderCache.put(order.getId(), order); } }

3.2 方案二:使用synchronized

public class OrderService { private Map<Long, Order> orderCache = new HashMap<>(); public synchronized void processOrder(Order order) { orderCache.put(order.getId(), order); } }

3.3 方案三:使用Collections.synchronizedMap

public class OrderService { private Map<Long, Order> orderCache = Collections.synchronizedMap(new HashMap<>()); public void processOrder(Order order) { orderCache.put(order.getId(), order); } }

四、常见JVM问题及解决方案

4.1 问题一:内存溢出(OOM)

现象

java.lang.OutOfMemoryError: Java heap space

排查步骤

# 1. 查看JVM参数 jps -v # 输出:12345 OrderService -Xms1g -Xmx1g # 2. 查看堆内存使用情况 jmap -heap 12345 # 3. 导出堆dump文件 jmap -dump:format=b,file=heap.hprof 12345 # 4. 使用MAT分析dump文件 # 下载地址:https://www.eclipse.org/mat/

解决方案

# 调整JVM堆内存大小 java -Xms2g -Xmx2g -jar app.jar # 或者 java -XX:+UseG1GC -Xms2g -Xmx2g -jar app.jar

4.2 问题二:内存泄漏

现象

  • 堆内存持续增长
  • Full GC频繁
  • 最终OOM

常见原因

// 原因1:静态集合 public class MemoryLeakDemo { private static List<Object> cache = new ArrayList<>(); // 永不释放 public void add(Object obj) { cache.add(obj); } } // 原因2:未关闭的资源 public void readFile() { InputStream is = new FileInputStream("file.txt"); // 忘记关闭is } // 原因3:ThreadLocal未清理 public class ThreadLocalDemo { private static ThreadLocal<Object> threadLocal = new ThreadLocal<>(); public void set(Object obj) { threadLocal.set(obj); // 线程结束后未清理,导致内存泄漏 } }

解决方案

// 解决方案1:使用弱引用 private static Map<Object, Object> cache = new WeakHashMap<>(); // 解决方案2:使用try-with-resources try (InputStream is = new FileInputStream("file.txt")) { // 使用is } catch (IOException e) { e.printStackTrace(); } // 解决方案3:ThreadLocal使用后清理 threadLocal.set(obj); try { // 使用threadLocal } finally { threadLocal.remove(); // 清理 }

4.3 问题三:频繁Full GC

现象

[Full GC (Allocation Failure) [PSYoungGen: 2048K->0K(2560K)] [ParOldGen: 69632K->69632K(69632K)]

排查步骤

# 查看GC日志 tail -f /var/log/gc.log # 查看GC统计信息 jstat -gcutil 12345 1000 10

解决方案

# 1. 使用G1垃圾收集器(推荐) java -XX:+UseG1GC -Xms2g -Xmx2g -jar app.jar # 2. 调整年轻代和老年代比例 java -Xms2g -Xmx2g -XX:NewRatio=2 -jar app.jar # 3. 调整 survivor 比例 java -Xms2g -Xmx2g -XX:SurvivorRatio=8 -jar app.jar

五、JVM参数调优

5.1 核心参数

# 堆内存 -Xms2g # 初始堆大小 -Xmx2g # 最大堆大小 # 年轻代 -Xmn1g # 年轻代大小 -XX:NewRatio=2 # 年轻代与老年代比例(2:1) -XX:SurvivorRatio=8 # Eden与Survivor比例(8:1) # 垃圾收集器 -XX:+UseG1GC # 使用G1(推荐) -XX:+UseParallelGC # 使用Parallel GC -XX:+UseConcMarkSweepGC # 使用CMS(JDK 14已废弃) # GC日志 -Xlog:gc*:file=gc.log:time,tags:filecount=5,filesize=10m # 其他 -XX:MaxDirectMemorySize=1g # 直接内存大小 -XX:MetaspaceSize=256m # 元空间大小

5.2 不同场景的推荐参数

# 场景1:中小型应用(1-2GB内存) java -Xms1g -Xmx1g -XX:+UseG1GC -jar app.jar # 场景2:大型应用(4-8GB内存) java -Xms4g -Xmx4g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar app.jar # 场景3:高并发应用(16GB+内存) java -Xms8g -Xmx8g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=8 -jar app.jar

六、监控工具

6.1 JDK自带工具

# jps:查看Java进程 jps -v # jstat:查看JVM统计信息 jstat -gcutil <pid> 1000 10 # jmap:查看堆内存 jmap -heap <pid> # jstack:查看线程堆栈 jstack <pid> # jcmd:多功能工具 jcmd <pid> VM.flags jcmd <pid> GC.heap_info

6.2 第三方工具

工具用途下载地址
Arthas在线诊断https://arthas.aliyun.com/
VisualVM监控分析https://visualvm.github.io/
MAT内存分析https://www.eclipse.org/mat/
JProfiler性能分析https://www.ej-technologies.com/products/jprofiler/

七、Arthas实战

7.1 安装Arthas

# 下载 curl -O https://arthas.aliyun.com/arthas-boot.jar # 启动 java -jar arthas-boot.jar # 选择进程 [INFO] arthas-boot version: 3.7.1 [INFO] Found existing java process, please choose one and hit RETURN. * [1]: 12345 OrderService

7.2 常用命令

# 查看线程 thread thread -n 3 # 查看CPU占用最高的3个线程 # 查看类加载 sc -d *Order* # 查看方法调用 monitor -c 5 com.example.OrderService processOrder # 查看堆栈 stack com.example.OrderService processOrder # 查看属性 getstatic com.example.OrderService cache # 热更新代码 jad --source-only com.example.OrderService > /tmp/OrderService.java mc /tmp/OrderService.java redefine /tmp/OrderService.class

八、性能调优清单

✅ JVM调优检查清单 ├── 1. 是否设置了合理的堆内存大小? │ └── 建议:Xms和Xmx设置相同值,避免动态扩容 ├── 2. 是否选择了合适的垃圾收集器? │ └── 建议:G1(通用)、Parallel(高吞吐)、ZGC(低延迟) ├── 3. 是否开启了GC日志? │ └── 建议:Xlog:gc*:file=gc.log ├── 4. 是否有内存泄漏? │ └── 建议:定期使用MAT分析dump文件 ├── 5. 是否有死锁? │ └── 建议:jstack检测死锁 ├── 6. 是否有频繁Full GC? │ └── 建议:调整堆大小或垃圾收集器参数 └── 7. 是否有CPU 100%? └── 建议:top + jstack定位问题线程

九、总结

今天我们学到了:

要点说明
CPU 100%top + jstack定位问题线程
内存溢出jmap + MAT分析dump文件
内存泄漏静态集合、未关闭资源、ThreadLocal
频繁Full GC调整堆大小、选择合适的GC
JVM参数Xms/Xmx、垃圾收集器、GC日志
监控工具jps/jstat/jmap/jstack、Arthas

彩蛋:小王用ConcurrentHashMap替代HashMap后,CPU从100%降到20%,接口响应时间恢复正常。他在群里发消息:“HashMap就像多人抢厕所——谁先进去谁先上,不排队,容易打架。ConcurrentHashMap就像智能厕所——有多个隔间,还有排队系统,秩序井然!”

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询