因为之前自己基于HEXO搭建的个人博客很久没有维护,就把以前的文章搬到上来。“滥芋充数”的同时还能复习下以前的笔记。废话不多说,还是先看下jstack和jstat的简单使用吧。
jstack的用法
jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID的Java堆栈信息。简单使用方法如下:
jstack pid
要看懂jstack输出的堆栈信息,首先要先知道堆栈信息中的名词的意思:
状态/动作 | 描述 |
---|---|
New | 当线程对象创建时存在的状态,此时线程不可能执行 |
Runnable | 当调用thread.start()后,线程变成为Runnable状态, 也就是就绪状态。只要得到CPU,就可以执行 |
Running | 线程正在执行 |
Waiting | 执行thread.join()或在锁对象调用obj.wait()等情况就会进该状态,表明线程正处于等待某个资源或条件发生来唤醒自己 |
Timed_Waiting | 执行Thread.sleep(long)、thread.join(long)或obj.wait(long)等就会进该状态,与Waiting的区别在于Timed_Waiting的等待有时间限制 |
Dead |
线程执行完毕,或者抛出了未捕获的异常之后,会进入dead状态,表示该线程结束 |
Deadlock |
表示有死锁 |
Waiting on condition |
等待某个资源或条件发生来唤醒自己。具体需要结合jstacktrace来分析,比如线程正在sleep,网络读写繁忙而等待 |
Blocked |
如果进入同步方法或同步代码块,没有获取到锁,则会进入该状态,表示阻塞 |
Waiting on monitor entry |
在等待获取锁 |
in Object.wait() |
获得了monitor之后,又调用了obj.wait() 方法,放弃 Monitor,进入 “Wait Set”队列 |
Waiting on monitor entry 和 in Object.wait()的详细描述:
Monitor是 Java中用于实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁,synchronized的底层就是基于monitor, 也就是monitor是sychronized的微观表现。每一个对象都有,也仅有一个 monitor。每个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的线程是 “Waiting for monitor entry”,而在 “Wait Set”中等待的线程是 “in Object.wait()”
blocked阻塞示例
public class JstackTest {
private static final Object mutex = new Object();
public static void main(String[] args) {
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
synchronized(mutex) {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(60);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
thread.setName("thread1");
thread.start();
synchronized (mutex) {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(60);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
运行代码后,先输出主线程名main, 主线程sleep 60秒, thread1等待main线程释放锁,下面使用jstack分析线程的dump日志:
D:\java\gitlab\jvm-debug>jstack 16212
2020-06-10 16:41:35
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.172-b11 mixed mode):
"thread1" #12 prio=5 os_prio=0 tid=0x0000000020979800 nid=0x22dc waiting for monitor entry [0x000000002173f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.github.shizhengchao.stack.err.JstackTest$1.run(JstackTest.java:14)
- waiting to lock <0x000000076b399b18> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
.....
"main" #1 prio=5 os_prio=0 tid=0x0000000004d23800 nid=0x32ac waiting on condition [0x0000000004b3f000]
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.github.shizhengchao.stack.err.JstackTest.main(JstackTest.java:30)
- locked <0x000000076b399b18> (a java.lang.Object)
从dump文件里看到thread1 waiting for monitor entry等待获取锁对象0x000000076b399b18, 所以状态是BLOCKED,而锁对象0x000000076b399b18此时正在被main线程所占有,但没有释放,所以线程main状态为TIMED_WAITING,需要等待一个释放锁的条件触发(waiting on condition).,我们设置的是sleep(60000)
deadlock死锁分析
死锁一般发生在重入锁里,两个线程互相占有对方需要的锁对象,都在等待对方释放锁。以下代码就是一个死锁,在多线程同时执行下可能发生死锁:
public class DeadLockThread extends Thread {
private boolean flag;
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public DeadLockThread(boolean flag, String name) {
this.flag = flag;
this.setName(name);
}
@Override
public void run() {
if(flag) {
synchronized(lock1) {
System.out.println(Thread.currentThread().getName() + "获得取锁1");
synchronized(lock2) {
System.out.println(Thread.currentThread().getName() + "获得取锁2");
}
}
} else {
synchronized(lock2) {
System.out.println(Thread.currentThread().getName() + "获得取锁2");
synchronized(lock1) {
System.out.println(Thread.currentThread().getName() + "获得取锁1");
}
}
}
}
}
// main里这样写:
DeadLockThread thread1 = new DeadLockThread(true, "thread1");
DeadLockThread thread2 = new DeadLockThread(false, "thread2");
thread1.start();
thread2.start();
运行代码后,执行jstack命令,发现了死锁:
D:\java\gitlab\jvm-debug>jstack 14116
2020-06-10 17:06:15
...
"thread2" #13 prio=5 os_prio=0 tid=0x0000000020b9f800 nid=0x29f4 waiting for monitor entry [0x0000000021abf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.github.shizhengchao.stack.err.DeadLockThread.run(DeadLockThread.java:29)
- waiting to lock <0x000000076b39bd60> (a java.lang.Object)
- locked <0x000000076b39bd70> (a java.lang.Object)
"thread1" #12 prio=5 os_prio=0 tid=0x0000000020b9c800 nid=0xdd4 waiting for monitor entry [0x00000000219bf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.github.shizhengchao.stack.err.DeadLockThread.run(DeadLockThread.java:21)
- waiting to lock <0x000000076b39bd70> (a java.lang.Object)
- locked <0x000000076b39bd60> (a java.lang.Object)
....
Found one Java-level deadlock:
=============================
"thread2":
waiting to lock monitor 0x000000001eb00c38 (object 0x000000076b39bd60, a java.lang.Object),
which is held by "thread1"
"thread1":
waiting to lock monitor 0x000000001eb03578 (object 0x000000076b39bd70, a java.lang.Object),
which is held by "thread2"
Java stack information for the threads listed above:
===================================================
"thread2":
at com.github.shizhengchao.stack.err.DeadLockThread.run(DeadLockThread.java:29)
- waiting to lock <0x000000076b39bd60> (a java.lang.Object)
- locked <0x000000076b39bd70> (a java.lang.Object)
"thread1":
at com.github.shizhengchao.stack.err.DeadLockThread.run(DeadLockThread.java:21)
- waiting to lock <0x000000076b39bd70> (a java.lang.Object)
- locked <0x000000076b39bd60> (a java.lang.Object)
Found 1 deadlock.
很显然,jstack已经为我们找到了一处死锁:Found 1 deadlock.线程thread2等待的monitor 0x000000001eb00c38 被thead1占用,线程hread1等待的monitor 0x000000001eb03578被thread2占用。
jstat的用法
jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。
命令的格式如下:
jstat [-命令选项] [pid] [间隔时间/毫秒] [查询次数]
类加载统计
C:\Users\Administrator> jstat -class 35012
Loaded Bytes Unloaded Bytes Time
430 881.6 0 0.0 0.07
名称 | 描述 |
---|---|
Loaded | 加载class的数量 |
Bytes | 所占用空间大小 |
Unloaded | 未加载数量 |
Bytes | 未加载占用空间 |
Time | 执行耗时 |
垃圾回收统计
C:\Users\Administrator> jstat -gc 35012
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
5120.0 5120.0 0.0 0.0 33280.0 3328.4 87552.0 0.0 4480.0 781.2 384.0 75.9 0 0.000 0 0.000 0.000
表头 | 描述 | jdk版本 |
---|---|---|
S0C | 新生代中Survivor space中S0当前容量的大小(KB) | - |
S1C | 新生代中Survivor space中S1当前容量的大小(KB) | - |
S0U | 新生代中Survivor space中S0容量使用的大小(KB) | - |
S1U | 新生代中Survivor space中S1容量使用的大小(KB) | - |
EC | Eden space当前容量的大小(KB) | - |
EU | Eden space容量使用的大小(KB) | - |
OC | Old space当前容量的大小(KB) | - |
OU | Old space使用容量的大小(KB) | - |
PC | Permanent space当前容量的大小(KB) | 1.8以下 |
PU | Permanent space使用容量的大小(KB) | 1.8以下 |
MC | MeteSpace 当前容量的大小(KB) | 1.8及以上 |
MU | MeteSpace 使用容量的大小(KB) | 1.8及以上 |
CCSC | 压缩类空间大小(KB) | 1.8及以上 |
CCSU | 压缩类空间大小(KB) | 1.8及以上 |
YGC | 从应用程序启动到采样时发生 Young GC 的次数 | - |
YGCT | 从应用程序启动到采样时 Young GC 所用的时间(秒) | - |
FGC | 从应用程序启动到采样时发生 Full GC 的次数 | - |
FGCT | 从应用程序启动到采样时 Full GC 所用的时间(秒) | - |
GCT | 从jvm启动到采样时用于垃圾回收的总时间(单位秒),它的值等于YGC+FGC | - |
总结垃圾回收统计
C:\Users\Administrator> jstat -gcutil 35012
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 0.00 10.00 0.00 17.44 19.76 0 0.000 0 0.000 0.000
表头 | 描述 |
---|---|
S0 | 幸存1区当前使用比例 |
S1 | 幸存2区当前使用比例 |
E | 伊甸园区使用比例 |
O | 老年代使用比例 |
M | 元数据区使用比例 |
CCS | 压缩使用比例 |
YGC | 年轻代垃圾回收次数 |
FGC | 老年代垃圾回收次数 |
FGCT | 老年代垃圾回收消耗时间 |
GCT | 垃圾回收消耗总时间 |