jstack命令详解【转】
jstack命令详解
简介
- jstack命令用于打印指定Java进程、核心文件或远程调试服务器的Java线程的Java堆栈跟踪信息[1]。
- jstack命令可以生成JVM当前时刻的线程快照。线程快照是当前JVM内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。
- 如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。
- 当指定的进程在64位Java虚拟机上运行时,可能需要指定-J-d64选项,例如:jstack -J-d64 -m pid。
- 该命令可能在未来的版本中不可用!!!
语法
jstack [ options ] pid
pid:Java进程的ID,可以通过jps命令查询到。
jstack [ options ] executable core
executable: 产生core dump的Java可执行程序
core:要打印的堆栈跟踪的核心文件
jstack [ options ] [ server-id@ ] remote-hostname-or-IP
server-id:当多个DEBUG服务器在同一远程主机上运行时,可使用的可选唯一ID。
remote-hostname-or-IP:远程DEBUG的服务器主机名或IP地址
options 参数说明
-F
当 jstack [-l] pid 没有响应时,强制打印一个堆栈转储。
-l
打印关于锁的其他信息,比如拥有的java.util.concurrent ownable同步器的列表。
-m
打印包含Java和本机C/ C++帧的混合模式堆栈跟踪。
-h
打印帮助信息
-help
打印帮助信息
例子
jstack pid
jstack -F pid
jstack -l pid
jstack -m pid
jstack -h pid
jstack -H pid
日志文件分析
- 可以通过 jstack [options] pid >> /xxx/xx/x/dump.log命令,将堆栈信息输出到dump.log文件后,然后下载到本地排查文件。
- 在dump.log日志文件里,需要重点关注的线程状态
Deadlock(死锁)
死锁线程,一般指多个线程调用间,进入相互资源占用,导致一直等待无法释放的情况。
1、死锁代码如下
package com.example;
public class Test {
public static void main(String[] args) {
Thread a = new MyThreadA();
Thread b = new MyThreadB();
a.setName("线程A");
a.start();
b.setName("线程B");
b.start();
}
}
class MyThreadA extends Thread {
@Override
public void run() {
System.out.println("================B===================");
synchronized (A.A) {
System.out.println("线程【" + Thread.currentThread().getName() + "】开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B.B) {
}
System.out.println("线程【" + Thread.currentThread().getName() + "】执行结束。B.B = " + B.B + "; A.A = " + A.A);
}
}
}
class MyThreadB extends Thread {
@Override
public void run() {
System.out.println("================B===================");
synchronized (B.B) {
System.out.println("线程【" + Thread.currentThread().getName() + "】开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (A.A) {
}
System.out.println("线程【" + Thread.currentThread().getName() + "】执行结束。B.B = " + B.B + "; A.A = " + A.A);
}
}
}
class A {
static Integer A = new Integer(1);
}
class B {
static Integer B = new Integer(1);
}
2、运行程序后,通过jstack -F pid >> /xxx/xx/x/a.log 将堆栈信息输出到a.log中
Attaching to process ID 9356, please wait...
Attaching to process ID 18972, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.221-b11
Deadlock Detection:
Found one Java-level deadlock:
=============================
"线程A":
waiting to lock Monitor@0x0000000018593468 (Object@0x00000000d66d0608, a java/lang/Integer),
which is held by "线程B"
"线程B":
waiting to lock Monitor@0x0000000018590c88 (Object@0x00000000d662c8f0, a java/lang/Integer),
which is held by "线程A"
Found a total of 1 deadlock.
Thread 4: (state = BLOCKED)
Thread 28: (state = BLOCKED)
- com.example.MyThreadB.run() @bci=69, line=47 (Interpreted frame)
Thread 27: (state = BLOCKED)
- com.example.MyThreadA.run() @bci=69, line=29 (Interpreted frame)
Thread 17: (state = BLOCKED)
Thread 16: (state = BLOCKED)
Thread 15: (state = BLOCKED)
- java.lang.Object.wait(long) @bci=0 (Interpreted frame)
- java.lang.ref.ReferenceQueue.remove(long) @bci=59, line=144 (Interpreted frame)
- java.lang.ref.ReferenceQueue.remove() @bci=2, line=165 (Interpreted frame)
- java.lang.ref.Finalizer$FinalizerThread.run() @bci=36, line=216 (Interpreted frame)
Thread 14: (state = BLOCKED)
- java.lang.Object.wait(long) @bci=0 (Interpreted frame)
- java.lang.Object.wait() @bci=2, line=502 (Interpreted frame)
- java.lang.ref.Reference.tryHandlePending(boolean) @bci=54, line=191 (Interpreted frame)
- java.lang.ref.Reference$ReferenceHandler.run() @bci=1, line=153 (Interpreted frame)
3、日志分析
Found one Java-level deadlock说明发现了一个死锁。
waiting to lock Monitor@0x0000000018593468 表示 线程A正在等待给Monitor@0x0000000018593468上锁,但是Monitor@0x0000000018593468被线程B持有。
waiting to lock Monitor@0x0000000018590c88 表示线程B正在等待给Monitor@0x0000000018590c88上锁,但是Monitor@0x0000000018590c88被线程A持有。
从上图可以知道,线程A正在等待锁,但是这个锁被线程B持有;而线程B也在等待锁,但是这个锁被线程A持有。线程A和现场B只能一直等待下去,造成了死锁。
state = BLOCKED 表明线程的状态是阻塞的。
通过com.example.MyThreadB.run() @bci=69, line=47 (Interpreted frame) 和com.example.MyThreadA.run() @bci=69, line=29 (Interpreted frame)定位到了出现死锁的具体的类和行数。
Waiting on condition(等待资源)
该状态出现在线程等待某个条件的发生。最常见的情况是线程在等待网络的读写,比如当网络数据没有准备好读时,线程处于这种等待状态。而一旦有数据准备好读之后,线程会重新激活,读取并处理数据。
如果发现有大量的线程都在处在 Wait on condition,从线程堆栈看,正等待网络读写,这可能是一个网络瓶颈的征兆,因为网络阻塞导致线程无法执行,原因有两种:一种情况是网络非常忙,几 乎消耗了所有的带宽,仍然有大量数据等待网络读写;另一种情况也可能是网络空闲,但由于路由等问题,导致包无法正常的到达。
另外一种出现 Wait on condition的常见情况是该线程在 sleep,等待 sleep的时间到了时候,将被唤醒。
代码和日志分析参见下文【Blocked(阻塞)】部分的分析。
Waiting on monitor entry(等待获取监视器)
Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor。
代码和日志分析参见下文【Blocked(阻塞)】部分的分析。
Blocked(阻塞)
线程阻塞,是指当前线程执行过程中,所需要的资源长时间等待却一直未能获取到,被容器的线程管理器标识为阻塞状态,可以理解为等待资源超时的线程。
1、代码
public class Test {
public static void main(String[] args) throws InterruptedException {
final Thread myThread = new Thread() {
@SneakyThrows
@Override
public void run() {
synchronized (this) {
System.out.println(Thread.currentThread());
TimeUnit.SECONDS.sleep(60);
}
}
}; // 到这一步,线程的状态是NEW
// 给线程起个名字,方便排查问题
myThread.setName("测试线程");
// 到这一步,线程的状态是RUNNABLE
myThread.start();
synchronized (myThread) {
System.out.println(Thread.currentThread());
TimeUnit.SECONDS.sleep(60);
}
}
}
2、运行程序后,通过jstack -l pid >> /xxx/xx/x/a.log 将堆栈信息输出到a.log中
2022-03-05 16:04:45
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.221-b11 mixed mode):
"测试线程" #12 prio=5 os_prio=0 tid=0x000000001b27b000 nid=0x1d68 waiting for monitor entry [0x000000001bbcf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.Test$1.run(Test.java:15)
- waiting to lock <0x00000000d6349040> (a com.example.Test$1)
Locked ownable synchronizers:
- None
"main" #1 prio=5 os_prio=0 tid=0x0000000003595800 nid=0x42d4 waiting on condition [0x000000000342f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at com.example.Test.main(Test.java:27)
- locked <0x00000000d6349040> (a com.example.Test$1)
Locked ownable synchronizers:
- None
......
3、日志分析
- 日志格式
- "测试线程"代表线程的名称。在实际开发中,要取一个和业务相关、见名知意的名字,便于排查问题。
- “prio=5”表示线程的优先级。
- “os_prio=0”表示操作系统级别的优先级。
- “tid=0x000000001b27b000”表示线程id。
- “nid=0x1d68”表示操作系统映射的线程id。
- “waiting for monitor entry”表示线程正在获取锁。
- “0x000000001bbcf000”表示线程栈的起始地址。
- 具体分析
程序运行后,输出信息如下图:
从程序输出信息可以看出,main线程首先获取到了"测试线程"对象上的锁,然后执行sleep操作,main线程的状态变为TIMED_WAINTING,而"测试线程" 并没有获取到这个锁,因此进入BLOCKED状态。
“测试线程”正在waiting to lock <0x00000000d6349040>,即试图在地址为0x00000000d6349040所在的对象获取锁;而该对象却被main线程加了锁(locked <0x00000000d6349040>)。
“main”线程正在waiting on condition [0x000000000342f000],说明正在等待某个条件触发。而“测试线程”waiting for monitor entry [0x000000001bbcf000],说明该线程正在等待获取锁。
转自
jstack命令详解 - 门罗的魔术师 - 博客园
https://www.cnblogs.com/powerwu/articles/16363552.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步