JVM分析(OOM、死锁、死循环)(JProfiler、arthas、jvm自带工具)

发布于:2025-09-04 ⋅ 阅读:(17) ⋅ 点赞:(0)

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=17Id=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)

参考文档:

JAVA项目线上CPU飙升的问题排查CPU飙升引发的问题: 系统相应迟钝:CPU占用过高时,系统处理请求的速度会变慢


网站公告

今日签到

点亮在社区的每一天
去签到