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)的基本流程

  1. 收集数据:

    • 使用 jstack <pid> 获取线程转储

    • 或通过 kill -3 <pid> 生成转储文件

    • 生产环境推荐使用多个时间点的快照(间隔5-10秒)

  2. 初步分类:

    • 按线程状态分组(RUNNABLE, BLOCKED, WAITING等)

    • 识别关键线程(如业务线程、GC线程等)

  3. 分析顺序:

    • 先分析阻塞(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工具分析热点方法
- 优化算法(如减少循环复杂度)
- 减少不必要的日志输出
  1. 死锁问题:通过jstackthread -b(Arthas)直接检测,重点关注BLOCKED线程的锁持有链。

  2. 锁竞争激烈:使用jstatArthas monitor命令统计锁竞争频率,优化锁粒度或改用并发工具类。

  3. 线程泄漏:结合jstack和内存dump分析,检查线程池的corePoolSize/maxPoolSize是否合理。

  4. 伪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 的信息,具体原因都需要结合代码来进行分析

posted @ 2025-06-05 23:17  Shafir莎菲尔  阅读(79)  评论(0)    收藏  举报