原来jdk自带了这么好玩的工具——使用 jstack定位死循环
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了原来jdk自带了这么好玩的工具——使用 jstack定位死循环相关的知识,希望对你有一定的参考价值。
参考技术A 线程快照是java虚拟机内每一个线程正在执行的方法堆栈的集合,生成线程快照的主要目的是用于定位线程出现问题的位置;常见的问题有命令格式
jstack的option参数并不多,真正用到的也就三个,接下来我们一个个介绍一下
- -F :当线程挂起(Suspended)时,使用jstack -l pid命令是不会打印堆栈信息的,使用-F则可以强制输出线程堆栈;但是会停止但
- -l :打印的信息除了堆栈外,还会显示锁的附加信息;
- -m :同时输出java和C/C++的堆栈信息;在java的系统类库里面,有很多方法都是native修饰的,这些native修饰的方法你在java层面是看不到源码的,因为这些方法都是C/C++实现的;
在线程的堆栈中,需要特别留意以下几种状态:
不带option参数的命令
打印结果如下
第一行各个单词的解析,
这里我们使用2个窗口,分别使用以下2个命令来测试
通过2个窗口对比可以看到,加了-l的命令多打印了锁的信息;
一般情况下,如果程序出错了, 都不会直接在生产环境的服务器上找错误,这个时候就可以用到一个非常实用的功能,将堆栈快照导出来,然后copy到别的电脑上看,命令如下
执行后,就可以看到文件已经导出来了
通过cat命令可以看到,里面的内容和我们在命令行输出的内容是一样的
首先我们准备好一个死循环的线程,在线程内定一个while的死循环,并且给这个线程起个名字为:yexindogn,阿里巴巴的开发规范里面有一个规定,就是每个线程必须起一个名字,起名字就是为了 以后程序出问题的时候好找错误;
接着我们将此代码打成jar包扔到linux服务器上运行,直接输入 java -jar Test.jar 命令即可运行,运行后我们可以看到控制台一直在输出112这个字符,这就代表程序已经在运行了;
接着在看下CPU的运行情况,使用top命令查看cpu占用情况,排在第一位的是进程号为30328的进程,占用了6.6%的cpu; 这边我使用了2个命令行连到同一台服务器,一个窗口用来运行刚刚的jar包,另一个窗口用来查找错误;
知道进程号了,接着就是找线程了,输入以下命令
打印结果如下,这里有一点需要注意,在我们加上-Hp指令后,PID展示就是线程的id了,这时候我们看到占用CPU最高的线程id是30365;
还有另一种方式,就是使用ps命令来查找线程
通过这个命令我们可以看到这边占用最高的线程id也是30365 ;
以上的方式我们成功找到了占用cpu高的线程id是30365,但这个id是十进制的,在这里需要先转为16进制,输入命令
计算出对应的16进制为:769d
当然也可以用其他的计算工具,比如mac系统自带计算器就支持进制之间的转换
在命令行输入以下命令,这种方法更加快速,推荐使用
其中,grep 命令是查找结果为769d的内容,-A 20 表示打印匹配所在行的后20行内容。直接帮我们定位到所在线程的堆栈,结果如下
当然也可以用下面的死办法,先打印出所有的堆栈快照;
打印结果如下
接着我用刚刚计算出来的16进制复制出来在这里搜索一下,经过查看就知道是我们刚刚起了名字为yexindong的线程出错了,出错的位置在Test.java的第13行代码
我们看看java代码,确实是第13行这里的死循环导致的
首先打开任务管理器,因为默认windows的任务管理器是不显示进程pid的,所以我们需要设置一下,选择 查看 选择列(S)...
选中PID进程表示符后点击确定按钮
然后我们就可以看到占用CPU最高的java进程PID为:976
因为windows不能直接查看java进程中的线程信息,所以我们需要借助一个工具,这个工具是微软自己开发的,叫做Process Explorer ,网上很多,需要的童鞋请自行百度,打开后找到pid为976的进程右击选择 属性
在弹出的窗口中找到线程这一栏,它的排序默认就是按照cpu占用的率倒序排列的,所以最上面的就是占用cpu最高的线程了,记住它的线程id:3548
刚刚拿到的进程id是十进制的,但是我们导出的jstack信息里面,线程id是以16进制来展示的,所以我们要先将这个线程id为3548转为16进制的,使用windows自带的计算器即可,在快捷命令行输入calc
计算器打开后将其设置为程序员使用的计算器
接着输入线程id3548,在按一下16进制,就会自动进行转换,结果为ddc,记住这个16进制;
在命令行输入以下指令导出进程的堆栈快照信息
几秒钟后,快照导出了,静静地躺在文件夹里,等待着我们打开
用Notepad++打开导出的文件,搜索刚刚计算出来的16进制ddc,就可以定位到线程出错的位置了
有些童鞋可能会觉得用这个jstack命令麻烦了,那java在代码里面可不可以打印出堆栈呢?你别说,还真有,就是这个方法:Thread.getAllStackTraces();光说不练假把式,来个demo测试一下吧
执行后打印结果如下,由此可以看到,将当前进程的所有线程都打印出来了,但是这边只打印了简单的堆栈信息,对于开发人员来说,已经起到了监控作用;
作为调优和找错的工具来说,可以说jstack是用的最多的一个工具了,但是由于局限性,现在已经慢慢被替换掉了;大家更倾向于使用阿里巴巴开发的工具arthas;感兴趣的童鞋可以了解下!
jstack的使用
jstack介绍
jstack是jdk自带的线程堆栈分析工具,使用该命令可以查看或导出 java 应用程序中线程堆栈信息。
jstack用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。 如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。另外,jstack工具还可以附属到正在运行的java程序中,看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现hung的状态,jstack是非常有用的.
1.使用jstat命令查看堆内存的使用情况
jstat 命令选项 vmid 间隔时间 查询次数
1.查看当前进程Class类加载的统计
jstat -class 16184
2.查看编译统计
jstat -compiler 16184
3.查看垃圾回收统计
jstat -gc 16184
s0c: 第一个Survivor区域大小
S1C:第二个Survivor区域的大小
S0U:第一个Survivor区域使用的大小
S1U:第二个Survivor区域使用的大小
EC:Eden区域的大小
EU:Eden区域的使用大小
OC:Old区的大小
OU:Old区使用的大小
MC:方法区大小
YGC:年轻代垃圾回收次数
FGC:年老代垃圾回收次数
2.通过jmap监控内存使用情况
监控堆内存:jmap -heap 16184
jstat 命令选项 vmid 间隔时间 查询次数
1.查看当前进程Class类加载的统计
jstat -class 16184
2.查看编译统计
jstat -compiler 16184
3.查看垃圾回收统计
jstat -gc 16184
s0c: 第一个Survivor区域大小
S1C:第二个Survivor区域的大小
S0U:第一个Survivor区域使用的大小
S1U:第二个Survivor区域使用的大小
EC:Eden区域的大小
EU:Eden区域的使用大小
OC:Old区的大小
OU:Old区使用的大小
MC:方法区大小
YGC:年轻代垃圾回收次数
FGC:年老代垃圾回收次数
2.通过jmap监控内存使用情况
监控堆内存:jmap -heap 16184
监控内存中对象的数量及其大小:
查看所有对象的数量以及大小包括类型:jmap -histo 16184| more
查看所有对象的数量以及大小包括类型:jmap -histo:live 16184| more
通过jmap导出堆内存使用情况的文件
jmap -dump:format=b,file=C:\\Windows\\dump.dat 16184
通过jhat查看dump文件并且进行分析,启动一个HTTP端口进行访问,通过该端口可以查看到整个应用程序所使用的的所有对象的情况,提供OQL进行检索
jhat -port 8081 C:\\Windows\\dump.dat
3.MAT分析工具,在工具中可以查看到对象数量以及内存使用的情况,当然可以分析出可能出现问题
如果问题是正常情况,可以加大内存,如果是非正常情况,手动解决代码问题
4.模拟内存溢出:
public static void main(String[] args) {
List<Object> objList=new ArrayList<>();
for (int i=0;i<10000000;i++){
String str="";
for(int j=0;j<1000;j++){
str+= UUID.randomUUID().toString();
}
objList.add(str);
}
System.out.println("添加数据成功~");
}
参数设置:-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
==================================================================================================================
1.通过jstack监控JVM当中线程的运行情况 jstack 进程ID
线程抢占CPU资源,当CPU过高时,定位线程,查看线程使用状态
线程状态:
初始状态:New,线程对象创建出来后,没有调用start方法,线程处于初始状态
运行状态:
1.就绪状态:Ready,调用了Start方法,等待CPU分配资源
2.运行状态:RUNNING,CPU分配资源给该线程,该线程处于运行状态
阻塞状态 BLOCKED:
线程获取资源,如果资源获取成功则正常运行,如果资源获取失败,就处于阻塞状态,等待什么时候获取到资源再变为运行状态
等待状态 WAITING:线程手动调用了wait()方法,或者join()方法,这些方法都是主动进入等待状态,等待状态会将CPU资源让渡
需要其他线程手动唤醒,notify(),notifyAll()唤起所有的等待线程
超时等待状态 TIMED_WAITING:与等待状态相同,都是主动进入等待,也是需要其他线程唤醒,但是区别在与超时等待,如果超过了等待时间,则自动唤醒
Thread.sleep(2000),在休眠等待时间内会将CPU资源让渡,然后等待时间结束自动进入运行状态
终止状态DIED:线程结束之后的状态
2.手动模拟死锁情况
public class LockTest {
//定义资源
private static Object obj1=new Object();
private static Object obj2=new Object();
//线程A:先获取到资源1,然后休眠2s,再获取资源2
private static class ThreadA implements Runnable{
@Override
public void run() {
synchronized (obj1){
System.out.println("ThreadA获取到了OBJ1资源");
通过jmap导出堆内存使用情况的文件
jmap -dump:format=b,file=C:\\Windows\\dump.dat 16184
通过jhat查看dump文件并且进行分析,启动一个HTTP端口进行访问,通过该端口可以查看到整个应用程序所使用的的所有对象的情况,提供OQL进行检索
jhat -port 8081 C:\\Windows\\dump.dat
3.MAT分析工具,在工具中可以查看到对象数量以及内存使用的情况,当然可以分析出可能出现问题
如果问题是正常情况,可以加大内存,如果是非正常情况,手动解决代码问题
4.模拟内存溢出:
public static void main(String[] args) {
List<Object> objList=new ArrayList<>();
for (int i=0;i<10000000;i++){
String str="";
for(int j=0;j<1000;j++){
str+= UUID.randomUUID().toString();
}
objList.add(str);
}
System.out.println("添加数据成功~");
}
参数设置:-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
==================================================================================================================
1.通过jstack监控JVM当中线程的运行情况 jstack 进程ID
线程抢占CPU资源,当CPU过高时,定位线程,查看线程使用状态
线程状态:
初始状态:New,线程对象创建出来后,没有调用start方法,线程处于初始状态
运行状态:
1.就绪状态:Ready,调用了Start方法,等待CPU分配资源
2.运行状态:RUNNING,CPU分配资源给该线程,该线程处于运行状态
阻塞状态 BLOCKED:
线程获取资源,如果资源获取成功则正常运行,如果资源获取失败,就处于阻塞状态,等待什么时候获取到资源再变为运行状态
等待状态 WAITING:线程手动调用了wait()方法,或者join()方法,这些方法都是主动进入等待状态,等待状态会将CPU资源让渡
需要其他线程手动唤醒,notify(),notifyAll()唤起所有的等待线程
超时等待状态 TIMED_WAITING:与等待状态相同,都是主动进入等待,也是需要其他线程唤醒,但是区别在与超时等待,如果超过了等待时间,则自动唤醒
Thread.sleep(2000),在休眠等待时间内会将CPU资源让渡,然后等待时间结束自动进入运行状态
终止状态DIED:线程结束之后的状态
2.手动模拟死锁情况
public class LockTest {
//定义资源
private static Object obj1=new Object();
private static Object obj2=new Object();
//线程A:先获取到资源1,然后休眠2s,再获取资源2
private static class ThreadA implements Runnable{
@Override
public void run() {
synchronized (obj1){
System.out.println("ThreadA获取到了OBJ1资源");
try {
//休眠2s,因为我们要将CPU资源让渡出去,这样线程B就可以先抢占obj2资源
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//休眠2s,因为我们要将CPU资源让渡出去,这样线程B就可以先抢占obj2资源
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2){
System.out.println("ThreadA获取到了OBJ2资源");
}
}
}
}
private static class ThreadB implements Runnable{
@Override
public void run() {
synchronized (obj2){
System.out.println("ThreadB获取到了OBJ2资源");
System.out.println("ThreadA获取到了OBJ2资源");
}
}
}
}
private static class ThreadB implements Runnable{
@Override
public void run() {
synchronized (obj2){
System.out.println("ThreadB获取到了OBJ2资源");
try {
//休眠2s,因为我们要将CPU资源让渡出去,这样线程B就可以先抢占obj2资源
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//休眠2s,因为我们要将CPU资源让渡出去,这样线程B就可以先抢占obj2资源
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1){
System.out.println("ThreadA获取到了OBJ1资源");
}
}
}
}
System.out.println("ThreadA获取到了OBJ1资源");
}
}
}
}
public static void main(String[] args) {
new Thread(new ThreadA()).start();
new Thread(new ThreadB()).start();
}
}
2.1监控当前程序的线程运行情况
jstack 进程ID,查看到如下信息:
Java stack information for the threads listed above:
===================================================
"Thread-1":
at com.wdksoft.LockTest$ThreadB.run(LockTest.java:41)
- waiting to lock <0x000000076bc9d060> (a java.lang.Object)
- locked <0x000000076bc9d070> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at com.wdksoft.LockTest$ThreadA.run(LockTest.java:22)
- waiting to lock <0x000000076bc9d070> (a java.lang.Object)
- locked <0x000000076bc9d060> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
new Thread(new ThreadA()).start();
new Thread(new ThreadB()).start();
}
}
2.1监控当前程序的线程运行情况
jstack 进程ID,查看到如下信息:
Java stack information for the threads listed above:
===================================================
"Thread-1":
at com.wdksoft.LockTest$ThreadB.run(LockTest.java:41)
- waiting to lock <0x000000076bc9d060> (a java.lang.Object)
- locked <0x000000076bc9d070> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at com.wdksoft.LockTest$ThreadA.run(LockTest.java:22)
- waiting to lock <0x000000076bc9d070> (a java.lang.Object)
- locked <0x000000076bc9d060> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
以上是关于原来jdk自带了这么好玩的工具——使用 jstack定位死循环的主要内容,如果未能解决你的问题,请参考以下文章
原来jdk自带了这么好玩的工具 > 使用 jstack定位死循环
原来jdk自带了这么好玩的工具 > 使用 jstack定位死循环