Java 线上排查标准手册:CPU 飙高、内存泄漏、接口慢,jstack/jmap/jstat 命令速查

发布时间:2026/6/29 18:04:42

Java 线上排查标准手册:CPU 飙高、内存泄漏、接口慢,jstack/jmap/jstat 命令速查 Java 线上排查标准手册CPU 飙高、内存泄漏、接口慢jstack/jmap/jstat 命令速查目录一、前置准备看一眼 top 确定方向二、CPU 飙高排查三、内存泄漏 / OOM 排查四、接口响应慢排查五、死锁排查六、线程数异常排查七、JVM 参数推荐八、命令速查总表一、前置准备看一眼 top 确定方向所有排查的第一步——确认问题类型# 找到 Java 进程 PID jps -l # 或 pgrep -f java # 看 CPU 和内存 top -p java_pidPID USER %CPU %MEM TIME COMMAND 12345 root 95.2 8.3 12:30.15 java决策树top 看到什么 → 走哪条路径 ───────────────────────────────── %CPU 80% 且持续 → 第二章「CPU 飙高」 %MEM 持续上涨 → 第三章「内存泄漏」 %CPU/%MEM 正常但慢 → 第四章「接口慢」 线程数异常增加 → 第六章「线程异常」二、CPU 飙高排查2.1 找到吃 CPU 的线程# -H显示线程级别 # -p指定进程 top -Hp java_pid输出示例PID USER %CPU %MEM TIME COMMAND 12399 root 92.1 0.1 5:30.15 java ← 这个线程吃了 92% CPU 12345 root 1.2 8.3 12:30.15 java 12401 root 0.5 0.1 0:10.22 java记下最忙线程的 PID这里是 12399。2.2 PID 转十六进制printf %x\n 12399 # 输出306f2.3 jstack 定位代码行# 打印线程栈grep 过滤目标线程 jstack java_pid | grep -A 30 0x306f输出解读http-nio-8080-exec-5 #42 daemon prio5 os_prio0 tid0x... nid0x306f runnable java.lang.Thread.State: RUNNABLE at com.example.OrderService.queryOrders(OrderService.java:87) at com.example.OrderController.list(OrderController.java:23) at javax.servlet.http.HttpServlet.service(HttpServlet.java:...) ...栈顶出现什么含义下一步你的业务代码 行号死循环或密集计算审查该行代码逻辑HashMap.get()/ArrayList.grow()集合操作不当检查数据量或 hash 冲突GC相关类内存不足触发频繁 GC转到第三章SocketInputStream.read()等待网络 I/O其实不是 CPU 问题看第四章2.4 持续采样确认# 每 3 秒采样一次共 5 次看是否卡在同一行 for i in $(seq 1 5); do echo Sample $i jstack java_pid | grep -A 5 0x306f sleep 3 done5 次都在同一行→ 死循环或密集计算改代码。每次在不同行→ 高并发请求打进来考虑扩容或限流。三、内存泄漏 / OOM 排查3.1 jstat 看 GC 概况# 每 1 秒输出一次共 10 次 jstat -gc java_pid 1000 10输出列说明S0C S1C S0U S1U EC EU OC OU MC MU Survivor区 Survivor区 Eden区 Eden区 老年代容量 老年代使用量 元空间 容量 使用量 容量 使用量重点关注jstat -gc java_pid 1000 5 | awk NR1 {print 老年代使用率 | YGC | YGCT | FGC | FGCT | GCT} NR1 { ou$8; oc$7; rate(oc0 ? ou/oc*100 : 0); printf %.1f%% | %4d | %5.2f | %3d | %5.2f | %5.2f\n, rate,$9,$10,$11,$12,$13 }指标正常值异常值含义老年代使用率 (OU/OC) 70% 90%即将 OOMFGCFull GC 次数每小时 5每几分钟一次内存泄漏导致频繁 GCFGCTFull GC 耗时 2s 10sGC 停顿严重影响响应YGCYoung GC 次数视吞吐而定每秒几十次新生代太小或对象分配过快3.2 jmap -histo 快速定位嫌疑类# 按实例数降序排列看前 30 个类 jmap -histo java_pid | head -30输出num #instances #bytes class name 1: 1827364 438567360 [C ← char[]通常是 String 2: 1523456 365629440 java.lang.String 3: 234567 56194080 java.util.HashMap$Node 4: 123456 29629440 com.example.dto.OrderDTO ← ⚠️ 你的 DTO 5: 98765 23703600 java.util.ArrayList分析思路char[]和String排前两名正常—— 任何 Java 应用都这样你的业务类DTO、Entity突然排进前 10—— 大概率是内存泄漏点HashMap$Node异常多—— 某个 Map 在不断增长却没清理3.3 jmap -dump 生成 heap dump# 生成 heap dump 文件 jmap -dump:formatb,file/tmp/heap_$(date %Y%m%d_%H%M%S).hprof java_pid⚠️ dump 期间 JVM 会暂停STW线上慎用。优先用-histo快速诊断。3.4 让 JVM 在 OOM 时自动 dump-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/dump/heap.hprof -XX:OnOutOfMemoryErrorkill -9 %p # OOM 后自动重启可选3.5 内存泄漏常见根因根因表现jmap -histo 特征ThreadLocal 未清理老年代缓慢增长业务 DTO 数量与请求量不成比例静态集合无限追加老年代持续增长ArrayList或HashMap实例数少但#bytes大数据库连接未关闭连接池耗尽不会 OOM但线程会 WAITING第三方库 bug特定类的实例异常多搜可疑的第三方类名四、接口响应慢排查4.1 看线程状态分布# 统计各状态的线程数量 jstack java_pid | grep java.lang.Thread.State | sort | uniq -c | sort -rn输出示例127 java.lang.Thread.State: WAITING (parking) 45 java.lang.Thread.State: TIMED_WAITING (parking) 12 java.lang.Thread.State: BLOCKED 8 java.lang.Thread.State: RUNNABLE状态多意味着什么BLOCKED锁竞争——有线程持有锁不放其他线程干等WAITING (parking)在等外部条件——数据库连接、Redis 响应、下游 HTTPTIMED_WAITING超时等待——可能是连接池耗尽的前兆RUNNABLE 少线程都在等不在干活——排查外部依赖4.2 找到阻塞源头# 看 BLOCKED 状态的线程在等哪个锁 jstack java_pid | grep -A 30 BLOCKED关键信息- waiting to lock 0x00000007a1b2c3d8 (a java.util.HashMap) - locked 0x00000007a1b2c3d8 (a java.util.HashMap) at ...持有锁的线程在干什么如果在做 I/O那就是锁粒度过大。4.3 检查数据库连接池# 看有多少线程在等 HikariCP 连接 jstack java_pid | grep -c HikariPool.*getConnection如果这个数字接近maximum-pool-size连接池满了。五、死锁排查5.1 jstack 自动检测死锁jstack java_pid | grep -A 50 deadlockjstack 会自动检测死锁并在输出末尾列出。如果存在死锁输出会明确标注Found one Java-level deadlock: Thread-1: waiting to lock monitor 0x00007f... (object 0x00000007a1b2c3d8, a java.lang.Object), which is held by Thread-2 Thread-2: waiting to lock monitor 0x00007f... (object 0x00000007a1b2c3e0, a java.lang.Object), which is held by Thread-1 Java stack information for the threads listed above: 5.2 如果没有自动检测# 手动搜 BLOCKED 线程及其等待的锁 jstack java_pid | grep -B 2 -A 10 waiting to lock六、线程数异常排查6.1 查看总线程数# 方式一 jstack java_pid | grep ^ | wc -l # 方式二查看线程列表 jcmd java_pid Thread.print | grep ^ | wc -l6.2 按线程名分组统计jstack java_pid | grep ^ | awk -F {print $2} | \ sed s/-[0-9]*$// | sort | uniq -c | sort -rn | head -20输出200 http-nio-8080-exec 50 SimpleAsyncTaskExecutor 30 scheduling- 10 HikariPool线程名异常数量可能原因http-nio-xxx-exec 200Tomcat 线程池配大了或请求积压SimpleAsyncTaskExecutor几千Async没配线程池每次新建线程HikariPool 连接池大小连接池泄露自定义线程名持续增长线程池没设 max-size 或线程未回收七、JVM 参数推荐# 生产环境基线 JVM 参数 java \ -Xms4g -Xmx4g \ # 堆大小minmax 避免动态扩缩 -XX:UseG1GC \ # G1 收集器大堆首选 -XX:MaxGCPauseMillis200 \ # GC 停顿目标 200ms -XX:HeapDumpOnOutOfMemoryError \ # OOM 自动 dump -XX:HeapDumpPath/data/dump/ \ # dump 路径 -XX:PrintGCDetails \ # GC 日志 -XX:PrintGCDateStamps \ -Xloggc:/data/logs/gc.log \ -jar app.jar八、命令速查总表症状 第一步命令 第二步 第三步 ──────────────────────────────────────────────────────────────────────────── CPU 飙高 top -Hp pid printf %x\n tid jstack pid | grep 0xhex 定位到代码行 内存上涨 jstat -gc pid 1000 10 jmap -histo pid jmap -dump:formatb,file... → 看 OU/OC 使用率 → 找嫌疑类 → 离线分析 Full GC 频繁 jstat -gc pid 1000 5 jmap -histo pid 同上 → 看 FGC 次数和耗时 → 确认泄漏 接口慢 jstack pid | grep jstack pid | grep 检查慢 SQL、连接池、 Thread.State -A 30 BLOCKED 下游 HTTP 超时 → 看状态分布 → 分析阻塞链 死锁 jstack pid | grep jstack 输出末尾自动 -A 50 deadlock 标注死锁详情 线程数异常 jstack pid | grep 按线程名分组统计 ^ | wc -l → 定位线程泄漏源 OOM 已发生 查看 hs_err_pid.log jmap -dump如仍在运行 MAT / JProfiler → JVM 崩溃日志 → 分析 dump → 找最大对象核心原则先确定症状类型再走对应路径。不要上来就 jstack 全量 dump 然后人肉分析——先分类再深入比一把梭快十倍。文章摘要本文系统整理了 Java 线上问题排查的标准命令和完整路径。覆盖六大场景CPU 飙高top -Hp → printf 转十六进制 → jstack 定位代码行、内存泄漏jstat 看 GC → jmap -histo 找嫌疑类 → jmap dump 离线分析、接口慢线程状态分布 → 阻塞链分析 → 连接池检查、死锁、线程数异常、OOM 应急处理。每个场景配有完整命令示例、输出解读表和决策逻辑文末附 JVM 生产参数推荐和命令速查总表。适合需要不依赖 Arthas 等第三方工具、用 JDK 自带命令完成排查的 Java 后端开发者。

相关新闻