JVM-栈-分析、调优-01
一、常见的问题分析定位
1.1 系统吞吐量明显下降,但 CPU 使用率不高( CPU 利用率低(如 30% 以下),请求响应变慢,吞吐量骤降,线程池任务堆积(如日志显示任务队列满))
大量线程因锁竞争进入 BLOCKED 状态,无法工作,剩余活跃线程数不足,无法充分利用 CPU
应优先分析 BLOCKED 状态的线程,
死锁或锁饥饿(Lock Starvation) 应优先分析 BLOCKED 状态的线程,
1.2 CPU 高时的分析优先级
| 线程状态 | 分析优先级 | 常见问题 | 工具/方法 |
|---|---|---|---|
| RUNNABLE | ⭐⭐⭐⭐ 最高 | 死循环、CPU 计算、伪 I/O 阻塞 | top -H + jstack + Arthas Profiler |
| BLOCKED | ⭐⭐ 中 | 锁竞争、死锁 | jstack 死锁检测 + Arthas thread -b |
| WAITING/TIMED_WAITING | ⭐ 低 | 线程泄漏、任务堆积 | jstack 统计 + 线程池监控 |
1.3 分析方法、顺序、流程
1. 分析线程栈(Dump)的基本流程
-
收集数据:
-
使用
jstack <pid>获取线程转储 -
或通过
kill -3 <pid>生成转储文件 -
生产环境推荐使用多个时间点的快照(间隔5-10秒)
-
-
初步分类:
-
按线程状态分组(RUNNABLE, BLOCKED, WAITING等)
-
识别关键线程(如业务线程、GC线程等)
-
-
分析顺序:
-
先分析阻塞(BLOCKED)线程
-
再分析等待(WAITING/TIMED_WAITING)线程
-
最后分析运行中(RUNNABLE)线程
-
2. 具体分析顺序和方法
RUNNABLE 状态分析
-
关注点:
-
长时间处于RUNNABLE的线程
-
CPU使用率高的线程
-
执行的方法是否合理
-
示例:
"http-nio-8080-exec-1" #31 daemon prio=5 os_prio=0 tid=0x00007f8a4c0e8000 nid=0x4a runnable [0x00007f8a1a7e7000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
分析方法:
-
检查是否在预期的方法中执行
-
查看是否有I/O阻塞(如socketRead)
-
结合CPU使用率判断是否正常
BLOCKED 状态分析
-
关注点:
-
锁竞争情况
-
死锁可能性
-
示例:
"Thread-2" #12 prio=5 os_prio=0 tid=0x00007f8a4c0b6000 nid=0x47 waiting for monitor entry [0x00007f8a1a9e8000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.LockDemo.methodB(LockDemo.java:25)
- waiting to lock <0x000000076bf0b0d0> (a com.example.LockDemo)
分析方法:
-
查找"waiting to lock"对应的锁对象
-
检查哪个线程持有该锁(查找"locked <相同地址>")
-
评估锁持有时间是否合理
WAITING/TIMED_WAITING 状态分析
-
关注点:
-
线程等待的原因
-
是否有可能的线程泄漏
-
示例:
"Thread-3" #13 prio=5 os_prio=0 tid=0x00007f8a4c0ba000 nid=0x48 in Object.wait() [0x00007f8a1aae9000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076bf0b0d0> (a com.example.LockDemo)
at java.lang.Object.wait(Object.java:502)
at com.example.LockDemo.methodC(LockDemo.java:30)
分析方法:
-
区分不同类型的等待:
-
Object.wait(): 等待notify/notifyAll -
Thread.join(): 等待其他线程结束 -
LockSupport.park(): 并发工具类的等待
-
-
检查是否有对应的唤醒机制
常见问题及解决方案
| 问题类型 | 特征 | 解决方案 |
|---|---|---|
| 死锁问题 | - 多个线程互相持有对方需要的锁 - 线程处于 BLOCKED状态且无法继续执行 |
- 修改锁获取顺序(统一顺序) - 使用 tryLock()设置超时- 减少同步代码块范围 |
| 锁竞争激烈 | - 多个线程频繁竞争同一把锁 - 大量线程处于 BLOCKED状态- CPU使用率不高但吞吐量低 |
- 减小锁粒度 - 使用读写锁( ReentrantReadWriteLock)- 考虑无锁数据结构 - 使用并发容器(如 ConcurrentHashMap) |
| 线程泄漏 | - 线程数持续增长 - 大量线程处于 WAITING/TIMED_WAITING状态- 可能伴随内存泄漏 |
- 检查线程池配置 - 确保有合理的超时设置 - 检查任务是否正常完成 |
| 伪装的CPU问题 | - CPU使用率高 - 线程处于 RUNNABLE状态- 实际执行效率低 |
- 使用Profiler工具分析热点方法 - 优化算法(如减少循环复杂度) - 减少不必要的日志输出 |
-
死锁问题:通过
jstack或thread -b(Arthas)直接检测,重点关注BLOCKED线程的锁持有链。 -
锁竞争激烈:使用
jstat或Arthas monitor命令统计锁竞争频率,优化锁粒度或改用并发工具类。 -
线程泄漏:结合
jstack和内存dump分析,检查线程池的corePoolSize/maxPoolSize是否合理。 -
伪CPU问题:通过火焰图(Async Profiler)定位热点代码,优化算法或I/O操作。
二、 常见的栈问题
关于 JVM 栈常见问题 的原因分析、排查方法和解决方案的总结表格,涵盖 栈溢出、死锁、CPU 高负载、响应时间长、阻塞/等待 等问题
JVM 栈常见问题及解决方案速查表
| 问题类型 | 典型原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 栈溢出 (StackOverflowError) |
- 递归调用无终止条件 - 栈帧过大(如局部变量过多) - 线程栈空间不足( -Xss 设置太小) |
1. 检查错误日志中的调用栈 2. 使用 jstack 分析递归深度3. 检查 -Xss 参数 |
1. 修复递归终止条件 2. 优化方法,减少局部变量 3. 增大 -Xss(如 -Xss2m) |
| 死锁 (Deadlock) |
- 多线程循环等待锁 - 锁获取顺序不一致 - 未设置锁超时 |
1. jstack 查找 BLOCKED 线程2. 使用 jcmd <pid> Thread.print 或 thread -b(Arthas) |
1. 统一锁获取顺序 2. 使用 tryLock(timeout)3. 减小锁粒度 |
| CPU 高负载 (高 us 时间) |
- 死循环或密集计算 - 锁自旋(轻量级锁竞争) - 频繁 GC(占用 CPU) |
1. top -H 定位高 CPU 线程2. jstack 查 RUNNABLE 线程3. Profiler 分析热点方法 |
1. 优化算法 2. 减少锁竞争 3. 调整 GC 参数(如 -XX:+UseG1GC) |
| 响应时间长 | - 锁竞争导致线程阻塞 - I/O 等待(如 DB/网络) - 栈帧过深(方法调用链长) |
1. 监控工具(APM/SkyWalking) 2. jstack 查 WAITING/BLOCKED 线程3. 分析调用链 |
1. 异步化 I/O 操作 2. 缓存结果 3. 优化方法调用层次 |
| 阻塞/等待 (BLOCKED/WAITING) |
- 锁竞争(synchronized)- 资源等待(如连接池满) - wait() 未唤醒 |
1. jstack 统计阻塞线程数2. 检查锁持有时间 3. 监控资源池(如 DB 连接池) |
1. 改用并发容器 2. 增加资源池大小 3. 检查 notify() 调用逻辑 |
三、演示
3.1 排查 死锁 (Deadlock) 有两种方式,使用 jstack 命令 或者使用thread
1、 使用jstack
命令:jstack pid |grep -i deadlock -A 30
首先需要知道pid
[root@demo]# jps --可知道运行项目的线程id
[root@demo]# jstack 1456 |grep -i deadlock -A 10 -- 执行命令,下方可以查看到deadlock 的线程
Java stack information for the threads listed above:
===================================================
--
at com.ee.aa.GiftApplication.lambda$testDeadLock$1(GiftApplication.java:50)
- waiting to lock <0x00000000e12f12e8> (a java.lang.Object)
- locked <0x00000000e12f12d8> (a java.lang.Object)
at com.test.demoGiftApplication$$Lambda$610/735937428.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"线程1":
at com.ee.aa.GiftApplication.lambda$testDeadLock$0(GiftApplication.java:36)
- waiting to lock <0x00000000e12f12d8> (a java.lang.Object)
- locked <0x00000000e12f12e8> (a java.lang.Object)
at com.ee.aa..GiftApplication$$Lambda$609/2104973502.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
2、 使用thread -b
需要启动arthas ,启动后,使用thrad -b查看
[root@demo]# thread -b ---未进入arthas服务 执行报找不到
-bash: thread: command not found
[root@demo]# java -jar arthas-boot.jar ---启动arthas 选择要进入的服务
[INFO] arthas-boot version: 3.4.5
[arthas@1456]$ thread -b ---执行命令
"线程2" Id=16 BLOCKED on java.lang.Object@650e3f77 owned by "线程1" Id=15
at com.test.a.GiftApplication.lambda$testDeadLock$1(GiftApplication.java:50)
- blocked on java.lang.Object@650e3f77
- locked java.lang.Object@2d0c9e8c <---- but blocks 1 other threads!
at com.test.a.GiftApplication$$Lambda$610/735937428.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
[arthas@1456]$ thread 15 ---执行命令查看线程1
3.2 排查 CPU高
监控:响应时间长
1、找到一个响应时间很长的接口,使用Jmeter设计脚本
2、执行Jmeter脚本,
3、使用arthas 监控调用的接口,看是否存在错误
[arthas@1389]$ trace <包路径> 方法名
# 查看方法调用栈(定位阻塞点)
stack <包路径.类名> 方法名
# 监控 JVM 整体状态(如线程阻塞)
dashboard
# 3. 如果发现耗时高,进一步trace
trace com.example.UserController getUserInfo '#cost > 1000' -n 3
监控:Blocked
1、使用Jmeter设计 出现Blocked 的接口
2、执行Jmeter脚本,
3、运行java 服务器上,
[root@demo]$ top ---查看
[root@demo]$ top ---查看
[root@test demo]# jps ---查看pid
1641 Jps
1389 jar
[root@test demo]# jstack 1389 |grep -i blocked ---查看Block
java.lang.Thread.State: BLOCKED (on object monitor)
java.lang.Thread.State: BLOCKED (on object monitor)
java.lang.Thread.State: BLOCKED (on object monitor)
java.lang.Thread.State: BLOCKED (on object monitor)
[root@test demo]# jstack 1389 |grep -i blocked -C 5
2025-06-05 21:30:48
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.211-b12 mixed mode):
"http-nio-18089-exec-15" #48 daemon prio=5 os_prio=0 tid=0x00007f792c00c800 nid=0x668 waiting for monitor entry [0x00007f793944a000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.test.democontroller.TestController.test2(b:86)
- waiting to lock <0x00000000e1a7ab58> (a com.test.democontroller.TestController)
at sun.reflect.GeneratedMethodAccessor61.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
--
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
"http-nio-18089-exec-13" #46 daemon prio=5 os_prio=0 tid=0x00007f792c00b000 nid=0x666 waiting for monitor entry [0x00007f793954b000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.test.democontroller.TestController.test2(b:86)
- waiting to lock <0x00000000e1a7ab58> (a com.test.democontroller.TestController)
at sun.reflect.GeneratedMethodAccessor61.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
--
- locked <0x00000000e1d508b0> (a sun.nio.ch.EPollSelectorImpl)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
at org.apache.tomcat.util.net.NioBlockingSelector$BlockPoller.run(NioBlockingSelector.java:331)
--
也可以保存到文件,下载文件查看
[root@demo]# jstack 1389 >1389.txt ----
[root@demo]# sz 1389.txt ----
使用如下工具 查看日志文件 (博客:https://blog.csdn.net/SmileSunshines/article/details/143786977)
| Jstack分析工具:IBM Thread and Monitor Dump Analyzer | IBM 开源工具,命令行/图形界面 |
Thread and Monitor Dump Analyzer for Java 是一类用于分析 Java 线程转储(Thread Dump)和监视器(Monitor)状态的工具,帮助开发者诊断 死锁、线程阻塞、CPU 高负载、应用无响应 等问题。
命令行工具
jstack:生成 Thread Dump。
[root@ demo]# jstack -l <PID> > thread_dump.txt
jcmd:更现代的替代品。
[root@ demo]# jcmd <PID> Thread.print > thread_dump.txt
------
生成 Thread Dump
# Linux/Mac
jstack <PID> > dump.txt
# 或使用 jcmd
jcmd <PID> Thread.print > dump.txt
# Windows(需先查找 PID)
jstack -l <PID> > dump.txt
分析示例
-
死锁检测:查找
Found one Java-level deadlock。 -
高负载线程:关注
RUNNABLE状态的线程栈。 -
锁竞争:检查
BLOCKED线程和waiting to lock <0x0000000712345678>。
----waiting 等待---
监控:waiting
操作步骤和上面一样,
主要是使用Jstack分析工具:IBM Thread and Monitor Dump Analyzer
查看 Thread detail ,查看waiting 的信息,具体原因都需要结合代码来进行分析

浙公网安备 33010602011771号