JVM分析(OOM、死锁、死循环)(JProfiler、arthas、jdk自带工具)
本文声明:
以下内容均为 JDK 8 + springboot 2.6.13 + (windows 11 或 CentOS 7.9.2009 )进行
ssh连接工具:FinalShell
请配合测试代码进行分析
一、测试代码
OOM
jvm参数:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heap/heapdump.hprof -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xms12M -Xmx12M -Xloggc:./log/gc-oomHeap.log
idea配置地方
需要自己创建heap文件夹和log文件夹
参数含义
-XX:+HeapDumpOnOutOfMemoryError
在JVM发生OutOfMemoryError(内存溢出)时自动生成堆转储文件-XX:HeapDumpPath=./heap/heapdump.hprof
指定堆转储文件的保存路径和文件名(当前目录下的heap文件夹中)-XX:+PrintGCDateStamps
在GC日志中打印具体的日期时间戳(而不仅仅是相对时间)-XX:+PrintGCDetails
打印详细的垃圾回收信息,包括GC前后内存变化、耗时等详细信息-Xms12M
设置JVM初始堆内存大小为12MB,方便发生OOM-Xmx12M
设置JVM最大堆内存大小为12MB(与-Xms相同,表示固定堆大小),方便发生OOM-Xloggc:./log/gc-oomHeap.log
指定GC日志的输出路径和文件名(当前目录下的log文件夹中)
代码:OOMTest(没有使用springboot,因为OOM报错也是分析dump文件,使用springboot和正常使用java命令行是一样的)
public class OOMTest {
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while (true){
list.add(new OOMObject());
}
}
@Data
static class OOMObject {
private byte[] bytes = new byte[1024];
}
}
死锁
代码:DeadLockTest2(springboot:配合多个线程分析)(为什么有个2,后面改了点代码已经打成jar包发linux,懒得改了)
@Service
public class DeadLockTest2 {
private static Object lockA = new Object();
private static Object lockB = new Object();
@PostConstruct
public void init() {
new Thread(()->{
synchronized (lockA){
try {
Thread.sleep(1000);
synchronized (lockB){
System.out.println("线程A 获取到 lockB");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"线程A").start();
new Thread(()->{
synchronized (lockB){
try {
Thread.sleep(1000);
synchronized (lockA){
System.out.println("线程B 获取到 lockA");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"线程B").start();
}
}
死循环
代码:DeadCycleTest(springboot:配合多个线程分析)
@Service
public class DeadCycleTest {
@PostConstruct
public void init(){
// 模拟有个线程一直在跑死循环,让springboot跑完main线程(启动的main方法)
new Thread(()->{
while (true){
new DeadCycle();
}
}, "dead-cycle").start();
}
static class DeadCycle{
}
}
二、jdk自带调优工具(通过命令行执行(在bash、cmd下执行))(我只讲我觉得比较重要的)(不是很好用,但要了解)(只要有jdk环境都能用)(以下案例在linux中完成)
工具所在目录:JDK的bin目录下
我只挑我认为比较重要的讲:JPS、JStack、JMap(Jcmd也行,相当于各个工具的整合升级版,可以自行学习)
jps:查看java项目进程pid
jps -l
jstack:使用jstack可查看指定进程(pid)的堆栈信息,用以分析线程情况(解决死锁、死循环)
命令 适用场景 jstack <pid>
常规线程状态检查,输出到文件分析 jstack -l <pid>
强烈推荐,用于检测死锁和获取更详细的锁信息 jstack -F <pid>
应用无响应时,强制生成线程转储 jstack -m <pid>
诊断 JVM 本地方法或 JNI 代码问题 线程状态:
线程状态 (Thread.State) 含义 产生原因(常见调用方法) 对CPU的影响 排查方向 RUNNABLE
可运行状态。线程正在 JVM 中执行代码,或准备就绪等待操作系统分配CPU时间片。 1. 正在执行计算逻辑。 2. 可运行的线程,等待OS调度。 高。如果长期处于此状态,很可能是计算密集型任务或陷入循环。 CPU高的罪魁祸首。查看堆栈,定位正在执行的方法。 BLOCKED
阻塞状态。线程等待获取一个监视器锁(monitor lock),以便进入一个 synchronized
同步块/方法。该锁正被其他线程持有。等待进入 synchronized
方法或代码块。低。线程未被调度,不消耗CPU。 性能瓶颈/锁竞争。查看堆栈,等待的是哪个锁,并找出持有该锁的线程。 WAITING
无限期等待。线程等待另一个线程执行某个特定的操作,没有时间限制。 Object.wait()
Thread.join()
LockSupport.park()
低。线程未被调度,不消耗CPU。 程序逻辑设计如此,或可能在等待一个永远不会发生的条件。查看堆栈明确等待条件。 TIMED_WAITING
有限期等待。线程等待另一个线程执行操作,但只等待指定的时间。 Thread.sleep(long)
Object.wait(long)
Thread.join(long)
LockSupport.parkNanos()
LockSupport.parkUntil()
低。线程在指定时间到期前不被调度。 通常是正常状态(如休眠、等待I/O超时)。如果大量线程在此状态,可能连接池满或下游服务响应慢。 NEW
初始状态。线程已被创建,但尚未启动( start()
方法还未被调用)。刚 new Thread()
之后,start()
之前。无 通常为瞬态,很少在快照中捕获到。 TERMINATED
终止状态。线程已经执行完毕,结束了生命周期。 run()
方法正常结束或因异常退出。无 通常为瞬态,很少在快照中捕获到。
jmap:查看堆内存使用情况、生成dump文件(一般来说是项目OOM挂了之后导出了dump文件(通过JVM参数:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heap/heapdump.hprof),用软件进行分析,因为在生产使用 jmap 会造成 STW ,所以一般不用命令行导出文件)
命令 作用 对应用影响 jmap -heap <pid>
查看堆配置和使用概况 小 jmap -histo <pid>
查看所有对象统计信息 小 jmap -histo:live <pid>
查看存活对象统计信息 大(触发Full GC)(不要在生产环境高峰期频繁执行,会造成应用暂停(STW),影响用户体验) jmap -dump:format=b,file=xxx.hprof <pid>
生成完整堆转储 大(可能阻塞,文件大) jmap -dump:live,format=b,file=xxx.hprof <pid>
生成存活对象堆转储 大(触发Full GC,文件较小)(不要在生产环境高峰期频繁执行,会造成应用暂停(STW),影响用户体验)
分析解决问题:
OOM:OOMTest(后面用 JProfiler 可视化工具分析,下文会讲,没必要用命令行分析(jhat可以分析,但jhat不够简单,功能也不够强大))
我们通过 JProfiler 可视化工具分析,不用命令行执行(我写的 JDK 参数,包含了出现OOM会自动导出dump文件,在生产环境也是把dump文件下载到 windows 环境(IOS也行)用可视化工具分析(还有其他的,比如 eclipse 的 MAT、Oracle 的 visualVM 和 jconsole))
死锁:DeadLockTest2
用linux项目跑起来后,打印文件进一个log文件进行分析,用 jsp 得到我们项目的进程 PID(26934)(Jps也是个java进程)
[root@iZf8z9l17126rhrd9o9x07Z test]# jps -l
12658 -- process information unavailable
26934 test-0.0.1-SNAPSHOT.jar
27036 sun.tools.jps.Jps
[root@iZf8z9l17126rhrd9o9x07Z test]# jstack -l 26934 > ./DeadLock.log
DeadLock.log 文件内容(关键内容,乱码不影响观看,是真实的日志):
"线程B" #17 prio=5 os_prio=0 tid=0x00007f9dccf35800 nid=0x69d8 waiting for monitor entry [0x00007f9db4d8f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.test.service.DeadLockTest2.lambda$init$1(DeadLockTest2.java:32)
- waiting to lock <0x00000000eea985f8> (a java.lang.Object)
- locked <0x00000000eea98460> (a java.lang.Object)
at com.example.test.service.DeadLockTest2$$Lambda$566/1702143276.run(Unknown Source)
at java.lang.Thread.run(Thread.java:750)
Locked ownable synchronizers:
- None
"线程A" #16 prio=5 os_prio=0 tid=0x00007f9dcceb1800 nid=0x69d7 waiting for monitor entry [0x00007f9db4e90000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.test.service.DeadLockTest2.lambda$init$0(DeadLockTest2.java:19)
- waiting to lock <0x00000000eea98460> (a java.lang.Object)
- locked <0x00000000eea985f8> (a java.lang.Object)
at com.example.test.service.DeadLockTest2$$Lambda$565/33533830.run(Unknown Source)
at java.lang.Thread.run(Thread.java:750)
Locked ownable synchronizers:
- None
Found one Java-level deadlock:
=============================
"线程B":
waiting to lock monitor 0x00007f9db00062c8 (object 0x00000000eea985f8, a java.lang.Object),
which is held by "线程A"
"线程A":
waiting to lock monitor 0x00007f9d80002178 (object 0x00000000eea98460, a java.lang.Object),
which is held by "线程B"
Java stack information for the threads listed above:
===================================================
"线���B":
at com.example.test.service.DeadLockTest2.lambda$init$1(DeadLockTest2.java:32)
- waiting to lock <0x00000000eea985f8> (a java.lang.Object)
- locked <0x00000000eea98460> (a java.lang.Object)
at com.example.test.service.DeadLockTest2$$Lambda$566/1702143276.run(Unknown Source)
at java.lang.Thread.run(Thread.java:750)
"线程A":
at com.example.test.service.DeadLockTest2.lambda$init$0(DeadLockTest2.java:19)
- waiting to lock <0x00000000eea98460> (a java.lang.Object)
- locked <0x00000000eea985f8> (a java.lang.Object)
at com.example.test.service.DeadLockTest2$$Lambda$565/33533830.run(Unknown Source)
at java.lang.Thread.run(Thread.java:750)
Found 1 deadlock.
可以看见上面信息:线程B、线程A的状态都被阻塞了java.lang.Thread.State: BLOCKED
,最后一行还是发现了一个死锁,从堆栈信息也可以看出是DeadLockTest2的19行和32行出问题了
死循环:DeadCycleTest
windows:
jprofiler 在线分析(这个后面说到jprofiler的时候说)
方法一:由于实际的业务一个死循环里面不可避免有对象的创建,而创建多了就会有gc,可以通过对象的创建消亡查看,并通过对象来定位
方法二:还可以通过cpu占用率和线程存活时间来鉴别,通过包>类>方法来定位
linux:
1、首先定位CPU占用进程较高的进程ID
使用top指令进行查看各个进程的资源占有情况,观察出CPU占有较高的进程ID
top
2、根据进程ID查找导致CPU飙升的线程信息( 26934 是 java 进程 PID )
ps H -eo pid,tid,%cpu | grep 26934
各参数含义
- `ps`:用于查看当前运行的进程状态。
- `H`:显示进程的线程层级(线程树)。`ps` 默认显示进程,但加上 `H` 后,它会显示每个进程的线程(如果该进程有多个线程的话),并以树状结构展示。
- `-eo pid, tid, %cpu`:
- `pid`:显示进程的 PID(进程标识符)。
- `tid`:显示线程的 TID(线程标识符),即线程的 ID。
- `%cpu`:显示该进程或线程使用的 CPU 百分比。
-`| grep 26934`:通过 `grep` 过滤,查找包含 `26934` 的行,通常用于筛选出与特定进程或线程相关的信息。
3、查找该线程的堆栈信息
首先将线程ID转换成16进制
[root@iZf8z9l17126rhrd9o9x07Z test]# printf "%x\n" 27094
69d6
线程id(27094)转换成16进制,输出结果为69d6
接着使用jstack指令来获取进程 ID 为 26934 的 Java 程序的所有线程堆栈信息
[root@iZf8z9l17126rhrd9o9x07Z test]# jstack 26934 | grep -A 10 69d6 > DeadLoop.log
生成进程ID为 26934 的所有线程堆栈信息,并且查找 69d6 后面的10行信息并打印
指令参数如下:
- `jstack 26934`:打印出Java程序中ID为 26934 的进程中所有线程的堆栈信息快照
- `|`:管道符,表示将 `jstack` 命令的输出传递给后续的命令
- `grep -A 10 69d6`:查找到 69d6 的后10行记录信息
- `grep` 进行文本搜索
- `-A 10` 显示后10行的信息
- `69d6` 搜索要求是 69d6
通过观察最后显示的进程为 26934,线程为 69d6 的堆栈信息情况,我们可以定位到导致CPU飙升的代码所在位置。
4、通过输出文件找问题:
DeadLoop.log
"dead-cycle" #15 prio=5 os_prio=0 tid=0x00007f9dcceb0800 nid=0x69d6 runnable [0x00007f9db4f91000]
java.lang.Thread.State: RUNNABLE
at com.example.test.service.DeadCycleTest.lambda$init$0(DeadCycleTest.java:14)
at com.example.test.service.DeadCycleTest$$Lambda$564/20853837.run(Unknown Source)
at java.lang.Thread.run(Thread.java:750)
"container-0" #14 prio=5 os_prio=0 tid=0x00007f9dcce6c000 nid=0x69d5 waiting on condition [0x00007f9db5092000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at org.apache.catalina.core.StandardServer.await(StandardServer.java:566)
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer$1.run(TomcatWebServer.java:197)
找代码 DeadCycleTest 类下的 14 行,找到改掉就好了
三、JProfiler(该工具是可视化工具,所以以下操作在windows环境下进行,但OOM一般都是从生产环境下载dump文件,再在windows下使用工具进行分析,跟我讲解的步骤类似)
// Download JProfiler 下载
// 适用于JProfiler最新版本的注册码,谁还会想要使用破解版呢。// v13
S-NEO_PENG#890808-g4tibemn0jen#37bb9// v14
S-J14-NEO_PENG#890808-1jqjtz91lywcp9#23624// v15
S-J15-NEO_PENG#890808-1a6eo5gvl1w9v#b6bab
OOM:OOMTest
OOMTest 跑起来并且报 OOM 退出后,我们得到了一个dump文件(hprof)
使用jprofiler打开
我们一般就找最大的对象byte[],右键选定对象:
找出他的引入对象:
挨个找,第一个不是我们自己写的,略过,找第二个
位置是 com.example.test.OOMTest 下的 main 方法,去代码里面找到,发现这个一直放到 list 里面去死循环,根据可达性算法,对象一直有被引用不能被gc,所以最后OOM了,修改此处代码再运行再分析
题外:java.lang.OutOfMemoryError: Java heap space
这是堆溢出了,还有些情况也会OOM,比如元空间溢出了java.lang.OutOfMemoryError: Metaspace
(概率很小近乎不可能,原因一般是类太多了,导致方法区满了,所以最好的办法是加内存)
下面是报错案例,可以自己去分析一下
JVM参数-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heap/heapdump.hprof -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xms64M -Xmx64M -XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=12M -Xloggc:./log/gc-oomHeap.log
-XX:MetaspaceSize=10M 元空间初始化容量 10M(一般不要用)
-XX:MaxMetaspaceSize=12M 元空间最大容量限制 12M(一般不要用)
其他的参数前面有
public class MetaSpaceOOMTest extends ClassLoader{
public static void main(String[] args) {
int i = 0;
MetaSpaceOOMTest metaSpaceOOMTest = new MetaSpaceOOMTest();
while (true){
// ClassWriter对象,用于创建类的二进制字节码
ClassWriter classWriter = new ClassWriter(0);
// 创建类的字节码,指明类的版本、修饰符、类名、父类、接口
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
// 返回类的字节码
byte[] code = classWriter.toByteArray();
// 类的加载
metaSpaceOOMTest.defineClass("Class" + i, code, 0, code.length);
i++;
}
}
}
死锁:DeadLockTest2
方法一:Attach分析(项目已经跑起来了,再进行监测)
打开我们跑的项目(启动中心>快速Attach>选择我们跑起来的项目):
选第二个Sampling
在 线程>线程历史 可以看见 线程A 和 线程B 被阻塞了
我们右键选择线程的热点树
就可以看见哪里出问题了
方法二:在Idea使用JProfiler插件,使用插件启动后,可以更直观看见哪里出现了死锁
再idea下载jprofiler插件
在右上角选择jprofiler运行项目,点击后会自动打开JProfiler:
查看当前锁状态图,把鼠标放到线上就可以看见哪出问题了
死循环:DeadCycleTest
jprofiler在线分析(方法一:由于实际的业务一个死循环里面不可避免有对象的创建,而创建多了就会有gc,可以通过对象的创建消亡查看,并通过对象来定位,方法二:还可以通过cpu占用率和线程存活时间来鉴别,通过包>类>方法来定位)
方法一:通过对象的大批量创建和销毁定位问题
先把对象转储再gc,再转储,对比两次对象的情况
可以看见,这个对象被大量的创建和销毁,通过全类目就可以定位到问题所在了
方法二:通过cpu占用率和线程存活时间来定位问题
在线程里面看见dead-cycle这个线程一直在跑
查看热点树
就可以定位问题所在了
四、arthas(用于生产环境定位问题所在,所以以下都是在linux环境下进行)
Arthas 是 Alibaba 开源的 Java 诊断工具,深受开发者喜爱。arthas官网
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
1、安装Arthas
[root@iZf8z9l17126rhrd9o9x07Z test]# wget https://arthas.aliyun.com/arthas-boot.jar;
--2025-09-03 13:45:44-- https://arthas.aliyun.com/arthas-boot.jar
正在解析主机 arthas.aliyun.com (arthas.aliyun.com)... 203.119.144.202
正在连接 arthas.aliyun.com (arthas.aliyun.com)|203.119.144.202|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度:144630 (141K) [application/java-archive]
正在保存至: “arthas-boot.jar”
100%[====================================================================================================================================================>] 144,630 --.-K/s 用时 0.1s
2025-09-03 13:45:45 (989 KB/s) - 已保存 “arthas-boot.jar” [144630/144630])
2、启动Arthas
选择第二个,第二个是我们启动的项目
[root@iZf8z9l17126rhrd9o9x07Z test]# java -jar arthas-boot.jar
[INFO] JAVA_HOME: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64/jre
[INFO] arthas-boot version: 4.0.5
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 12658 -- process information unavailable
[2]: 26934 test-0.0.1-SNAPSHOT.jar
2
出现下面的 banner 就说准备就绪了
OOM:OOMTest(还是使用下载dump文件,在JProfiler中排查的方式进行)
死锁:DeadLockTest2
1、查看线程使用情况
使用thread
(太多了,截取了一点)查看线程使用情况,使用thread -b
(完整的)找出当前阻塞其他线程的线程
[arthas@26934]$ thread
Threads Total: 37, NEW: 0, RUNNABLE: 11, BLOCKED: 2, WAITING: 14, TIMED_WAITING: 5, TERMINATED: 0, Internal threads: 5
ID NAME GROUP PRIORITY STATE %CPU DELTA_TIME TIME INTERRUPTED DAEMON
15 dead-cycle main 5 RUNNABLE 99.63 0.199 216:48.203 false false
-1 C1 CompilerThread1 - -1 - 0.37 0.000 0:2.602 false true
44 arthas-command-execute system 5 RUNNABLE 0.15 0.000 0:0.005 false true
18 lettuce-timer-3-1 main 5 TIMED_WAITING 0.09 0.000 0:6.148 false true
-1 VM Periodic Task Thread - -1 - 0.07 0.000 0:9.875 false true
16 线程A main 5 BLOCKED 0.01 0.000 0:0.357 false false
-1 C2 CompilerThread0 - -1 - 0.01 0.000 0:8.571 false true
17 线程B main 5 BLOCKED 0.01 0.000 0:0.328 false false
[arthas@26934]$ thread -b
"线程B" Id=17 BLOCKED on java.lang.Object@2d11bb3d owned by "线程A" Id=16
at com.example.test.service.DeadLockTest2.lambda$init$1(DeadLockTest2.java:32)
- blocked on java.lang.Object@2d11bb3d
- locked java.lang.Object@1b294e9c <---- but blocks 1 other threads!
at com.example.test.service.DeadLockTest2$$Lambda$566/1702143276.run(Unknown Source)
at java.lang.Thread.run(Thread.java:750)
2、我们逐句分析thread -b
的内容
"线程B" Id=17 BLOCKED on java.lang.Object@2d11bb3d owned by "线程A" Id=16
"线程B"
和"线程A"
:线程的名称。Id=17
和Id=16
:JVM 分配给线程的唯一ID。BLOCKED
:这是线程的状态。BLOCKED
状态意味着“线程B”正在积极地等待获取一个监视器锁(monitor lock,即synchronized
锁),但这个锁目前被其他线程持有。它无法继续执行,直到获得这个锁。on java.lang.Object@2d11bb3d
:这是“线程B”想要获取的锁对象的地址(@
后面的十六进制数是对象在内存中的哈希码)。owned by "线程A" Id=16
:明确指出这个锁(@2d11bb3d
)当前正被“线程A”持有。第一行总结: “线程B” 被阻塞了,因为它想拿到一个锁,而这个锁正在被 “线程A” 占着。
2.
at com.example.test.service.DeadLockTest2.lambda$init$1(DeadLockTest2.java:32)
- 这指出了“线程B”被阻塞时正在执行的代码位置。
- 它停在
DeadLockTest2
类的lambda$init$1
方法中(这通常是一个Lambda表达式),具体是在这个文件的第32行。- 第32行很可能是一个
synchronized(lock)
代码块,或者是一个调用了synchronized
方法的地方。
3.
- blocked on java.lang.Object@2d11bb3d
- 这再次确认了第一行的信息:“线程B”正在等待锁对象
@2d11bb3d
。
4.
- locked java.lang.Object@1b294e9c <---- but blocks 1 other threads!
- 这是最关键的一行,它揭示了死锁的核心。
locked java.lang.Object@1b294e9c
:虽然“线程B”在等待锁A(@2d11bb3d)
,但它自己已经持有了另一个锁B(@1b294e9c)
。<---- but blocks 1 other threads!
:JVM 给你的一个重要提示! 它告诉你,“线程B”持有的这个锁B(@1b294e9c)
正在阻塞着另外 1 个线程(很可能就是“线程A”),导致那个线程无法执行。
5.
at com.example.test.service.DeadLockTest2$$Lambda$566/1702143276.run(Unknown Source) at java.lang.Thread.run(Thread.java:750)
- 这是线程的调用栈,显示它是从一个Lambda表达式开始,最终通过
Thread.run
运行的。
死循环:DeadCycleTest
1、查看所有线程的CPU耗时排名(显示繁忙的线程)
thread -i 1000
:统计最近 1000ms 内的线程 CPU 时间。可以看见 dead-cycle 线程 cpu占用率 98.99(太多了,截取了一点)
[arthas@26934]$ thread -i 1000
Threads Total: 37, NEW: 0, RUNNABLE: 11, BLOCKED: 2, WAITING: 14, TIMED_WAITING: 5, TERMINATED: 0, Internal threads: 5
ID NAME GROUP PRIORITY STATE %CPU DELTA_TIME TIME INTERRUPTED DAEMON
15 dead-cycle main 5 RUNNABLE 98.99 0.990 231:6.170 false false
-1 VM Periodic Task Thread - -1 - 0.05 0.000 0:10.547 false true
-1 C1 CompilerThread1 - -1 - 0.04 0.000 0:2.680 false true
18 lettuce-timer-3-1 main 5 TIMED_WAITING 0.03 0.000 0:6.468 false true
44 arthas-command-execute system 5 RUNNABLE 0.02 0.000 0:0.028 false true
2、定位问题位置
方法一、通过上诉的线程信息,查看线程id为15的完整调用堆栈
thread 15
可以看见问题所在代码的 DeadCycleTest 类的 14 行
[arthas@26934]$ thread 15
"dead-cycle" Id=15 RUNNABLE
at com.example.test.service.DeadCycleTest.lambda$init$0(DeadCycleTest.java:14)
at com.example.test.service.DeadCycleTest$$Lambda$564/20853837.run(Unknown Source)
at java.lang.Thread.run(Thread.java:750)
方法二、列出 1000ms 内最忙的 5 个线程栈
thread -n 5 -i 1000
:列出 1000ms 内最忙的 5 个线程栈。(我这展示两条进行对比就行了),可以看见 dead-cycle 的 cpu 占用率99.86%,也可以看见问题所在代码的 DeadCycleTest 类的 14 行
[arthas@26934]$ thread -n 5
"dead-cycle" Id=15 cpuUsage=99.86% deltaTime=200ms time=13489381ms RUNNABLE
at com.example.test.service.DeadCycleTest.lambda$init$0(DeadCycleTest.java:14)
at com.example.test.service.DeadCycleTest$$Lambda$564/20853837.run(Unknown Source)
at java.lang.Thread.run(Thread.java:750)
"lettuce-timer-3-1" Id=18 cpuUsage=0.02% deltaTime=0ms time=6331ms TIMED_WAITING
at java.lang.Thread.sleep(Native Method)
at io.netty.util.HashedWheelTimer$Worker.waitForNextTick(HashedWheelTimer.java:600)
at io.netty.util.HashedWheelTimer$Worker.run(HashedWheelTimer.java:496)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:750)
参考文档: