自动内存管理机制(5)- 虚拟机性能监控
0. 概述
在我们日常开发的项目中,有时经常会碰到以下问题:
- OOM(OutOfMemoryError),内存不足
- 内存泄漏
- 线程死锁
- Lock Contention,锁争用
- Java进程消耗CPU过高…
通常我们使用的最简单的解决方法就是调大内存,但这样的话只是解决了这一次的问题,对于它为什么会出现的原因就置之不理了(或者说不会去深究问题根源)。本文将对一些常用的JVM性能调优、监控工具进行介绍,经常使用适当的虚拟机监控和分析工具可以加快我们分析数据、定位解决问题的速度。
一共有以下几种常用的JDK监控和故障处理工具:
名称 | 主要作用 |
---|---|
jps | JVM Process Status Tool,显示制定系统内所有的HotSpot虚拟机进程 |
jstat | JVM Statistics Monitoring Tool,用于收集HotSpot虚拟机各方面的运行数据 |
jinfo | Configuration Info for Java,显示虚拟机配置信息 |
jmap | Memory Map for Java,生成虚拟机的内存转储快照(heapdump文件) |
jhat | JVM Heap Dump Browser,用于分析heapdump文件,它会建立一个HTTP/HTML服务器,让用户可以在浏览器上查看分析结果 |
jstack | Stack Trace for Java,显示虚拟机的线程快照 |
注意:以下所有内容均基于JDK1.8进行测试
1. jps:虚拟机进程状况工具
jps命令和UNIX的ps命令非常像:可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class,main()函数所在的类)名称以及这些进程的本地虚拟机的唯一ID。
语法如下:
jps [options] [hostid]
如果不指定hostid
就默认为当前主机或者服务器。
命令行参数选项options
说明如下:
选项 | 作用 |
---|---|
-q | 只输出LVMID,省略主类的名称 |
-m | 输出虚拟机进程启动时传递给主类main()函数的参数 |
-l | 输出主类的全名,如果进程执行的时Jar包,输出Jar路径 |
-v | 输出虚拟机进程启动时JVM参数 |
执行结果:
[C:\~]$ jps -l
9328 org.jetbrains.jps.cmdline.Launcher
10520 org.jetbrains.idea.maven.server.RemoteMavenServer
7912 sun.tools.jps.Jps
1580 com.yis.catcher.demo.Demo1
7308
2. jstat:虚拟机统计信息监视工具
jstat(JVM Statistics Monitoring Tool)是用来监视虚拟机各种运行状态信息的命令行工具。它可以显示虚拟机进程中的类加载、内存、垃圾收集、JIT编译等运行数据。
语法如下:
jstat [option vmid [interval[s|ms] [count]] ]
-
vimd
是Java虚拟机ID,在Linux/Unix系统上一般就是进程ID。 -
interval
是采样时间间隔。 -
count
是采样数目如果省略
interval
和count
则代表只查询一次。 -
option
代表类型,主要分为三类:类加载、垃圾收集、运行期编译情况,有以下的具体选项:
比如下面输出的是GC信息,每隔250毫秒查询一次,一共查询5次:
[C:\~]$ jstat -gc 1580 250 5
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 30803.8 87552.0 0.0 4480.0 775.8 384.0 76.4 0 0.000 0 0.000 0.000
5120.0 5120.0 0.0 0.0 33280.0 30803.8 87552.0 0.0 4480.0 775.8 384.0 76.4 0 0.000 0 0.000 0.000
5120.0 5120.0 0.0 0.0 33280.0 30803.8 87552.0 0.0 4480.0 775.8 384.0 76.4 0 0.000 0 0.000 0.000
5120.0 5120.0 0.0 0.0 33280.0 30803.8 87552.0 0.0 4480.0 775.8 384.0 76.4 0 0.000 0 0.000 0.000
5120.0 5120.0 0.0 0.0 33280.0 30803.8 87552.0 0.0 4480.0 775.8 384.0 76.4 0 0.000 0 0.000 0.000
先来看JVM堆内存的分布:
堆内存 = 新生代 + 老年代 + 永久代(方法区)
新生代 = Eden区 + 两个Suivivor区(From 和 To)
现在再来看上面具体的各项含义:
S0C、S1C、S0U、S1U:Survivor 0/1区容量(Capacity)和使用量(Used)
EC、EU:Eden区容量和使用量
OC、OU:年老代容量和使用量
MC、MU:永久代容量和使用量
CCSC、CCSU:压缩类容量和使用量
YGC、YGT:年轻代GC次数和GC耗时
FGC、FGCT:Full GC次数和Full GC耗时
GCT:GC总耗时
3. jinfo:Java配置信息工具
jinfo是用来实时查看和调整虚拟机各项参数。
语法如下:
jinfo [option] pid
执行样例:查询CMSinitiatingOccupancyFraction参数值
[C:\~]$ jinfo -flag CMSInitiatingOccupancyFraction 1580
-XX:CMSInitiatingOccupancyFraction=-1
对于Windows平台,jinfo的的功能仍有较大限制,只提供了最基本的-flag
选项
4. jmap:Java内存映像工具
jmap命令用于生成堆转储快照,查看堆内存使用状况,一般配合jhat使用。
语法如下:
jmap [option] vmid
option
可用的选项如下:
jmap的功能在windos平台下是受限的,除了-dump
和-histo
可用,其他都不可用。
jmap进行dump命令格式如下:
jmap -dump:format=b,file=dumpFileName pid
对10424进程进行Dump:
[C:\~]$ jmap -dump:format=b,file=E:\dumpDemo.dat 10424
Dumping heap to E:\dumpDemo.dat ...
Heap dump file created
5. jhat:虚拟机堆转储快照分析工具
jhat命令与jmap搭配使用,来分析jmap生成的堆转储快照。
示例:
[C:\Users\Yisany\Desktop]$ jhat -port 9998 dumpDemo.dat
Reading from dumpDemo.dat...
Dump file created Wed Nov 14 10:36:30 CST 2018
Snapshot read, resolving...
Resolving 19457 objects...
Chasing references, expect 3 dots...
Eliminating duplicate references...
Snapshot resolved.
Started HTTP server on port 9998
Server is ready.
此时在浏览器中输入 http://localhost:9998/ 就可以看到结果。
6. jstack:Java堆栈跟踪工具
jstack用于生成虚拟机当前时刻的线程快照。生成线程快照的主要目的是定位线程出现长时间停顿的原因。
线程快照:当前虚拟机每一条线程正在执行的方法堆栈的集合。
线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在做什么。
语法如下:
jstack [option] pid
option
的可选项如下:
以下摘自JVM性能调优监控工具jps、jstack、jmap、jhat、jstat、hprof使用详解
jstack可以定位到线程堆栈,根据堆栈信息我们可以定位到具体代码,所以它在JVM性能调优中使用得非常多。下面我们来一个实例找出某个Java进程中最耗费CPU的Java线程并定位堆栈信息,用到的命令有ps、top、printf、jstack、grep。
第一步先找出Java进程ID,我部署在服务器上的Java应用名称为mrf-center:
root@ubuntu:/# ps -ef | grep mrf-center | grep -v grep
root 21711 1 1 14:47 pts/3 00:02:10 java -jar mrf-center.jar
得到进程ID为21711,第二步找出该进程内最耗费CPU的线程,可以使用ps -Lfp pid或者ps -mp pid -o THREAD, tid, time或者top -Hp pid,我这里用第三个,输出如下:
TIME列就是各个Java线程耗费的CPU时间,CPU时间最长的是线程ID为21742的线程,用
printf "%x\n" 21742
得到21742的十六进制值为54ee,下面会用到。
OK,下一步终于轮到jstack上场了,它用来输出进程21711的堆栈信息,然后根据线程ID的十六进制值grep,如下:
root@ubuntu:/# jstack 21711 | grep 54ee
"PollIntervalRetrySchedulerThread" prio=10 tid=0x00007f950043e000 nid=0x54ee in Object.wait() [0x00007f94c6eda000]
可以看到CPU消耗在PollIntervalRetrySchedulerThread这个类的Object.wait(),我找了下我的代码,定位到下面的代码:
// Idle wait
getLog().info("Thread [" + getName() + "] is idle waiting...");
schedulerThreadState = PollTaskSchedulerThreadState.IdleWaiting;
long now = System.currentTimeMillis();
long waitTime = now + getIdleWaitTime();
long timeUntilContinue = waitTime - now;
synchronized(sigLock) {
try {
if(!halted.get()) {
sigLock.wait(timeUntilContinue);
}
}
catch (InterruptedException ignore) {
}
}
它是轮询任务的空闲等待代码,上面的sigLock.wait(timeUntilContinue)就对应了前面的Object.wait()。