自动内存管理机制(5)- 虚拟机性能监控

自动内存管理机制(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是采样数目

    如果省略intervalcount则代表只查询一次。

  • 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,我这里用第三个,输出如下:

img

​ 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()。


猜你喜欢

转载自blog.csdn.net/yisany_Q/article/details/84321112