原博客路径:https://www.cnblogs.com/rainy-shurun/p/5732341.html
JDK本身提供了很多方便的JVM性能调优监控工具,除了集成式的VisualVM和jConsole外,还有jps、jstack、jmap、jhat、jstat等小巧的工具。
现实企业级Java开发中,有时候我们会碰到下面这些问题:
-
OutOfMemoryError,内存不足
-
内存泄露
-
线程死锁、死循环等
-
Java进程消耗CPU过高
-
.系统挂起无响应
- 系统运行越来越慢
- 系统挂起无响应
- 由于线程数量太多导致内存溢出(如无法创建线程等等)
A、 jps(Java Virtual Machine Process Status Tool)
jps主要用来输出JVM中运行的进程状态信息。语法格式如
1 jps [options] [hostid]
命令行参数选项说明如下: 如果不指定hostid就默认为当前主机或服务器。
1 -q 不输出类名、Jar名和传入main方法的参数
2 -m 输出传入main方法的参数
3 -l 输出main类或Jar的全限名
4 -v 输出传入JVM的参数
比如下面:
1 root@ubuntu:/# jps -m -l
2 2458 org.artifactory.standalone.main.Main /usr/local/artifactory-2.2.5/etc/jetty.xml
3 29920 com.sun.tools.hat.Main -port 9998 /tmp/dump.dat
4 3149 org.apache.catalina.startup.Bootstrap start
5 30972 sun.tools.jps.Jps -m -l
6 8247 org.apache.catalina.startup.Bootstrap start
7 25687 com.sun.tools.hat.Main -port 9999 dump.dat
8 21711 mrf-center.jar
jstack主要用来查看某个Java进程内的线程堆栈信息。可以分析除了内存泄露之外的其他任何问题,比如:
1、系统无缘无故的cpu过高
2、系统挂起,无响应
3、系统运行越来越慢
4、性能瓶颈(如无法充分利用cpu等)
5、线程死锁,死循环等
6、由于线程数量太多导致的内存溢出(如无法创建线程等)
所以线程栈是分析jvm虚拟机中常用到的,也就是jstack会是我们以后工作中常用到的命令。在具体分析的时候因为线程栈是瞬时快照包含线程状态以及调用关系,所以借助堆栈信息可以帮助分析很多问题,比如线程死锁,锁争用,死循环,识别耗时操作等等。线程栈是瞬时记录,所以没有历史消息的回溯,一般我们都需要结合程序的日志进行跟踪
下面来科普下啥是线程栈:
(1)什么是线程栈?
线程堆栈也称线程调用堆栈,是虚拟机中线程(包括锁)状态的一个瞬间状态的快照,即系统在某一个时刻所有线程的运行状态,包括每一个线程的调用堆栈,锁的持有情况。
(2)、线程栈包含的3个方面
线程名字,id,线程的数量等。
线程的运行状态,锁的状态(锁被哪个线程持有,哪个线程在等待锁等)
调用堆栈(即函数的调用层次关系)调用堆栈包含完整的类名,所执行的方法,源代码的行数
一个例子:
(3)、线程栈的7种状态
1、NEW
2、RUNNABLE
3、RUNNING
4、BLOCKED
5、WAITING
6、TIMED_WAITING
7、TERMINATED
jstack可以定位到线程堆栈,根据堆栈信息我们可以定位到具体代码,所以它在JVM性能调优中使用得非常多。下面我们来一个实例找出某个Java进程中最耗费CPU的Java线程并定位堆栈信息,用到的命令有ps、top、jstack、grep。
第一步先找出Java进程ID,我部署在服务器上的Java应用名称为mrf-center: 得到进程ID为21711,第二步找出该进程内最耗费CPU的线程,可以使用ps -Lfp pid或者ps -mp pid -o THREAD, tid, time或者top -Hp pid,我这里用第三个,输出如下:
TIME列就是各个Java线程耗费的CPU时间,CPU时间最长的是线程ID为21742的线程,用
OK,下一步终于轮到jstack上场了,它用来输出进程21711的堆栈信息,然后根据线程ID的十六进制值grep,如下:
1 root@ubuntu:/# jstack 21711 | grep 54ee
2 "PollIntervalRetrySchedulerThread" prio=10 tid=0x00007f950043e000 nid=0x54ee in Object.wait() [0x00007f94c6eda000]
可以看到CPU消耗在PollIntervalRetrySchedulerThread这个类的Object.wait(),我找了下我的代码,定位到下面的代码:
01 // Idle wait
02 getLog().info("Thread [" + getName() + "] is idle waiting...");
03 schedulerThreadState = PollTaskSchedulerThreadState.IdleWaiting;
04 long now = System.currentTimeMillis();
05 long waitTime = now + getIdleWaitTime();
06 long timeUntilContinue = waitTime - now;
07 synchronized(sigLock) {
08 try {
09 if(!halted.get()) {
10 sigLock.wait(timeUntilContinue);
11 }
12 }
13 catch (InterruptedException ignore) {
14 }
它是轮询任务的空闲等待代码,上面的sigLock.wait(timeUntilContinue)就对应了前面的Object.wait()。
C、 jmap(Memory Map)和jhat(Java Heap Analysis Tool)
jmap用来查看堆内存使用状况,用于分析内存溢出的问题,常常结合jhat使用。
- 使用jmap -heap pid(java进程)查看进程堆内存使用情况,包括使用的GC算法、堆配置参数和各代中堆内存使用情况。这个不常用。
- 使用jmap -histo[:live] pid查看堆内存中的对象数目、大小统计直方图,如果带上live则只统计活对象,利用这个命令查找与项目有关的类,与项目有关的一般在类前面会有公司项目有关的包名,比如这里的cn.test.TestBean
class name是对象类型,说明如下:
B byte
C char
D double
F float
I int
J long
Z boolean
[ 数组,如[I表示int[]
[L+类名 其他对象
还有一种会遇到的情况是在前面没有找到与项目有关的使用项目有关的类,这个时候就要使用jmap -dump命令了,但是不要开始就要这个命令,因为这个命令会触发full gc把内存清理部分。
当用jmap把进程内存使用情况dump到文件中后,后面再用jhat分析查看。jmap进行dump命令格式如下:
B byte
C char
D double
F float
I int
J long
Z boolean
[ 数组,如[I表示int[]
[L+类名 其他对象
我们对java进程ID为21711进行Dump: jmap -dump:live,format=b,file=heap.bin 21711(java进程id)
上面红线框出来的部分大家可以自己去摸索下,最后一项支持OQL(对象查询语言)。
D、jstat(JVM统计监测工具)
语法格式如下:
1 |
jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ] |
vmid是虚拟机ID,在Linux/Unix系统上一般就是进程ID。interval是采样时间间隔。count是采样数目。比如下面输出的是GC信息,采样时间间隔为250ms,采样数为4:
1 |
root@ubuntu:/ # jstat -gc 21711 250 4 |
2 |
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT |
3 |
192.0 192.0 64.0 0.0 6144.0 1854.9 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649 |
4 |
192.0 192.0 64.0 0.0 6144.0 1972.2 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649 |
5 |
192.0 192.0 64.0 0.0 6144.0 1972.2 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649 |
6 |
192.0 192.0 64.0 0.0 6144.0 2109.7 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649 |
要明白上面各列的意义,先看JVM堆内存布局:
从图可以看出:
堆内存 = 年轻代 + 年老代 + 永久代
年轻代 = Eden区 + 两个Survivor区(From和To)
现在来解释各列含义:
S0C、S1C、S0U、S1U:Survivor 0/1区容量(Capacity)和使用量(Used)
EC、EU:Eden区容量和使用量
OC、OU:年老代容量和使用量
PC、PU:永久代容量和使用量
YGC、YGT:年轻代GC次数和GC耗时
FGC、FGCT:Full GC次数和Full GC耗时
GCT:GC总耗时
对应参数解释:
- java.lang.Thread.State: BLOCKED (on object monitor)
- java.lang.Thread.State: WAITING (parking):一直等那个条件发生;
- java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定时的,那个条件不到来,也将定时唤醒自己。
- java.lang.Thread.State: TIMED_WAITING (on object monitor);
- java.lang.Thread.State: WAITING (on object monitor);