jstack 命令
- 什么是jstack
- jstack命令
- jstack实战操作
什么是jstack
jstack是用于生成java虚拟机当前时刻的线程快照。线程快找是当前java虚拟机内存每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因
如果出现死锁,死循环,请求外部资源出现长时间等待等,线程出现停顿的时候,通过jstack来查看各个线程调用堆栈,就知道线程在后台做了什么,或者等待什么资源,如果java程序崩溃生成core文件,jstack工具可以用来获取core文件的java,stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程
程序触发生成的问题,另外,jstack工具还可以附属到正在运行的java程序中看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现bug的状态,jstack是非常有用的。
jstack命令
参数说明:
- -F 当 ‘jstack [-l] pid’ 没有响应的时候强制打印栈信息,如果直接jstack无响应时,用于强制jstack,一般情况下无需使用
- -l 长列表 打印关于锁定附加信息,例如属于:java.util.concurrent的ownable synchronizers列表会使jvm停顿的长久的多,
- -m 打印java和native c/c++ 框架的所有栈信息.可以打印JVM的堆栈,显示上Native的栈帧,一般应用排查不需要使用
执行命令
jstack -m 12905
科普一下线程状态:
NEW | 未启动的。不会出现在Dump中 |
RUNNABLE | 在虚拟机内执行的 |
BLOCKED | 受阻塞并等待监视器锁 |
WATING | 无限期等待另一个线程执行特定操作。 |
TIMED_WATING | 有时限的等待另一个线程的特定操作 |
TERMINATED | 已退出的 |
jstack实战操作
现在假设一个场景,在几乎没有什么调用的情况下,服务器的cpu的使用率一直居高不下非常有可能是程序中出现死循环导致的,当然在业务量不大的情况下,如果cpu占用率过高,除了死循环以外,常见的还有,内存泄漏,导致大量的full GC
jstack实战之cpu 占用过高-----测试代码
/**
* @Auther: corey
* @Date: 2020/8/5 14:25
* @Description: jstack 测试代码
*/
public class JstackTest {
private static Executor executor = Executors.newFixedThreadPool(5);
private static final Object lock = new Object();
public static void main(String[] args) {
MyRunnableImpl myRunnable = new MyRunnableImpl();
MyRunnableImpl myRunnable1 = new MyRunnableImpl();
executor.execute(myRunnable);
executor.execute(myRunnable1);
}
static class MyRunnableImpl implements Runnable{
@Override
public void run() {
synchronized (lock){
//死循环
calculate();
}
}
private void calculate(){
int i = 0;
while (true){
i++;
}
}
}
}
使用top命令可以看到,cpu的占用到99%了,使用 jps命令查看当前java 程序运行的有哪些。
然后使用 jstack 4275 命令查看当前程序出现死循环的是在哪一行
如下上图,JstackTest 程序的 31行
①线程名。
②线程优先级。
③一个地址(该地址是什么地址,存疑)。
④线程十六进制id。
⑤线程状态。
⑥线程当前所处方法。
⑦该箭头表示线程的执行历程。
jstack实战之死锁线程的定位-----测试代码
/**
* @Auther: corey
* @Date: 2020/8/5 15:01
* @Description:死锁线程的定位
*/
public class JstackLockTest {
private static Executor executor = Executors.newFixedThreadPool(5);
private static final Object lockA = new Object();
private static final Object lockB = new Object();
public static void main(String[] args) {
MyRunnableImplOne th1 = new MyRunnableImplOne();
MyRunnableImplTwo th2 = new MyRunnableImplTwo();
executor.execute(th1);
executor.execute(th2);
}
static class MyRunnableImplOne implements Runnable {
@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "获得了锁LockA");
//循环
int i = 10;
while (i > 10) {
i--;
}
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "获得了锁B");
}
}
}
}
static class MyRunnableImplTwo implements Runnable {
@Override
public void run() {
synchronized (lockB) {
System.out.println(Thread.currentThread().getName()+"获得了锁lockB");
int i = 10;
while (i > 10) {
i--;
}
synchronized (lockA) {
System.out.println(Thread.currentThread().getName()+"获得了锁lockA");
}
}
}
}
}
输出如下:
如上图所示没有继续输出;继续排查
- 1 使用 jps 命令查看
使用jstack pid指令,查看指定进程的堆栈信息,观察并定位到死锁线程。
上图可以看到,线程 pool-1-thread-2已经获取到了地址 <0x000000076ae309a0> 正在 java.lang.Thread.State: BLOCKED 等待获取地址为 <0x000000076ae309b0> 的对象锁,
第二个如图所示:
pool-1-thread-1 已经获取到了地址为<0x000000076ae309b0>的地址,正在BLOCKED等待地址为 <0x000000076ae309a0>的对象锁
上述分析过程是对死锁和cpu 超高的情况下的场景分析,我们可以通过诸如【统一获取锁的顺序(线程按照一定的顺序加锁)】、【设置获取锁的超时时间(线程尝试获, 取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)】、【死锁检测】等手段预防死锁发生。但是如果一旦发生了死锁就没法解开了,只能停掉程序,修复bug后再启动服务了。