必须要会的JVM性能监测工具(JVisualVM)
# 前言
JVisualVM是一个Java虚拟机的监控工具,要是需要对JVM的性能进行监控可以使用这个工具哦
使用这个工具,你就可以监控到java虚拟机的gc过程了
那么,这么强大的工具怎么下载呢?
在JDK1.6后的版本是自带这个工具,它就在你的jdk的bin目录上
如果是默认安装的JDK,一般就在C盘,Program Files的java目录,就会看到你的jdk版本,点进去之后打开bin这个文件夹,就可以看到这个软件了。
VisualVM使用简单,几乎0配置,功能还是比较丰富的,几乎囊括了其它JDK自带命令的所有功能。
- 内存信息
- 线程信息
- Dump堆(本地进程)
- Dump线程(本地进程)
- 打开堆Dump。堆Dump可以用jmap来生成。
- 打开线程Dump
- 生成应用快照(包含内存信息、线程信息等等)
- 性能分析。 :idea: CPU分析(各个方法调用时间,检查哪些方法耗时多),内存分析(各类对象占用的内存,检查哪些类占用内存多)
# 启动JVisualVM
启动方法:
1.进入jdk安装目录的bin目录,双击打开这个程序
2.菜单键+R,输入cmd进入命令行模式,输入命令jvisualvm 启动程序。注:要是使用命令行启动的软件,命令框可不能关闭哦,关闭了的话JVisualVM也会被关闭,切记切记!!!
启动程序之后进入这个界面,这个就是JVisualVM的使用界面了。
# 使用VisualVM
# 本地进程
本地进程可以直接连接,如果只是监控本地的java进程,是不需要配置参数的,直接打开就能够进行监控。首先我们需要在本地打开一个Java程序,例如我打开IDEA,这时在jvisualvm界面就可以看到与IDEA相关的Java进程了:
# 远程进程
服务器进程需要在启动的时候加上JVM参数。
服务器启动java进程时配置
-Djava.rmi.server.hostname=0.1.2.3
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=1099
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
2
3
4
5
其中,0.1.2.3代表本机ip,1099代表开启的用于远程监听的本地端口号,authenticate=false代表不认证,ssl=false代表不适用ssl。
启动举例:
java
-Djava.rmi.server.hostname=0.1.2.3 -Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=1099
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-jar
xxxxx.jar
2
3
4
5
6
7
这样就在服务器开启了1099不认证端口,用于性能检测使用。
添加远程主机:
打开JMX连接:
添加远程主机的ip和端口号,端口是JVM 中的 Dcom.sun.management.jmxremote.port。
点击一个进程,就可以看到该进程的概述信息,该进程的JVM参数以及系统属性等信息都能够查看到:
点击 “监视” 就能够看到CPU、内存、类以及线程的活动状况,点击右上角的 “堆Dump” 就能够导出内存映像文件:
点击 “线程” 就能够看到该进程内部的所有线程,以及线程的运行状况等信息。如果点击右上角的 “线程Dump” 就会导出一个内容与jstack打印内容一致的文件:
#
JVisualVM 中线程状态(运行/休眠/等待/驻留/监视)解析
各状态含义如下:
- 运行(runnable):正在运行中的线程。
- 休眠(timed_waiting):休眠线程,例如调用Thread.sleep方法。
- 等待(waiting):等待唤醒的线程,可通过调用Object.wait方法获得这种状态,底层实现是基于对象头中的monitor对象。
- 驻留(waiting):等待唤醒的线程,和等待状态类似,只不过底层的实现方式不同,处于这种状态的例子有线程池中的空闲线程,等待获取reentrantLock锁的线程,调用了reentrantLock的condition的await方法的线程等等,底层实现是基于Unsafe类的park方法,在AQS中有大量的应用。
- 监视(blocked):等待获取monitor锁的线程,例如等待进入synchronize代码块的线程。
# 代码测试
private static final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
private static final ReentrantLock reentrantLockTest = new ReentrantLock();
public static void main(String[] args) {
//基于println方法中的synchronize代码块测试运行或者监视线程
Thread thread1 = new Thread(() -> {
while (true) {
System.out.println("运行或者监视线程1");
}
}, "运行或者监视线程1");
thread1.start();
//基于println方法中的synchronize代码块测试运行或者监视线程
Thread thread2 = new Thread(() -> {
while (true) {
System.out.println("运行或者监视线程2");
}
}, "运行或者监视线程2");
thread2.start();
//monitor对象等待线程
Object lock = new Object();
Thread thread3 = new Thread(() -> {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "等待线程synchronized");
thread3.start();
//reentrantLock中的条件对象调用await方法线程为驻留线程
ReentrantLock reentrantLock = new ReentrantLock();
Condition condition = reentrantLock.newCondition();
Thread thread4 = new Thread(() -> {
reentrantLock.lock();
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}, "等待线程reentrantLock");
thread4.start();
//休眠线程
Thread thread5 = new Thread(() -> {
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "休眠线程");
thread5.start();
Thread thread6 = new Thread(ThreadTest::lockMethod, "reentrantLock运行线程");
thread6.start();
//等待获取reentrantLock的线程为驻留线程
Thread thread7 = new Thread(() -> {
try {
TimeUnit.MICROSECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
lockMethod();
}, "reentrantLock监视线程");
thread7.start();
//线程池中的空闲线程为驻留线程
singleThreadExecutor.execute(() -> {
//线程池中的线程是懒加载,需要运行任务之后才会产生线程
System.out.println("驻留线程运行");
});
}
private static void lockMethod() {
reentrantLockTest.lock();
try {
while (true) {
}
} finally {
reentrantLockTest.unlock();
}
}
//println源码也简单贴一下
//java.io.PrintStream#println(java.lang.String)
public void println(String x) {
//this表示System.out这个PrintStream对象
synchronized (this) {
print(x);
newLine();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
以上代码运行之后,打开JVirtualVM查看线程如下:
根据代码的顺序从上至下讲:
- 两个运行或者监视线程的System.out.println()语句因为抢同一个synchronize锁,可以很明显的看出是在交替运行,状态分别处于运行和监视状态。
- 等待线程synchronize因为调用了Object的wait方法,一直处于等待状态。
- 休眠线程省略。
- 重点是和reentrantLock锁相关的三个线程,注意下上图中有一个地方写错了,等待线程reentrantLock,实际应该是驻留线程reentrantLock,可以看到无论是通过condition的await方法,还是在阻塞等待锁的过程中,都是处于驻留状态,而不是我一开始预想的等待状态,通过查看源码后发现它们最终都调用了Unsafe的park方法,后续的线程dump也能验证这一点。
- pool- 1-threadpool-1就是那个线程池,因为里面的线程处于空闲状态,也属于驻留。
# 线程dump
点击 “抽样器” 界面中的 “CPU ” 就可以动态的看到每个方法的执行时间,当我们的代码执行的比较慢了,就可以通过抽样器来查看是哪一个方法执行的比较慢:
而点击 “内存” 的话,就可以实时的、动态的查看到每个类实例对象的数量以及这些实例所占用的内存大小:
在Profiler界面上,可以对CPU、内存进行性能分析,分析后会给出分析结果:
# 安装插件
使用之前,我们需要安装一个插件,来更好的来观察虚拟机的性能,点击上方的工具-插件
在可用插件那里选择下载,安装一个VIsual GC的插件
一般会报错,因为默认的链接已经给转移了,需要在设置那里把默认的链接更改
点击设置,编辑,把URL更改一下
那URL填什么呢?先确定一下自己的jdk版本号,然后用以下链接去查看URL
确认版本号,可以菜单键+R,执行cmd,输入java -version来查看自己的版本号
比如我的是201
那就在这个网站:https://visualvm.github.io/pluginscenters.html 找到自己版本号的地址,复制URL到设置那里
比如我的是JDK8的201,所以应该是131-291之间,所以我就复制下面那行蓝色的URL到设置的定制器中
然后就可以下载想要的插件啦
然后重启一下即可看到有visual GC这个选项了
# 代码测试
第一次观察
几秒钟后观察
我把这个程序停止掉之后,最后进行观察,左边的test这个java程序就不见了,右边的GC也就停了下来。
那现在就开始分析一下这几个过程,就看最后关掉之后的那个状态,可以看到GC time是指发生了多少次的GC,图中就是发生了233次GC,就花了276.256ms的时间,而下一行的Eden区,也是发生了223次GC,花费的时间也是276.256ms,很显然,发生的GC都是在Eden区,Old老年代区发生了0次GC,花费0s。
这只是个普通的死循环,工作量并不大,所以占用不了多少内存空间,根本就不会发生多少次GC,也根本不需要老年代区GC
而右边的进度图,就是说明内存使用的情况,当图中的色块达到顶端的时候,就是内存满的时候,这时候就需要进行一次GC,把内存占用推送到下一个区,满一次清理一次就GC一次。
# 将堆dump的文件,使用其进行查看。
1)堆dump文件。
2)将dump文件传至jvisualvm本机,点击”文件“-》”装入“,选择第一步生成的dump文件。
a.摘要标签
可查看dump的各项信息。
文件->装入->堆Dump->检查 -> 查找20保留大小最大的对象,就会触发保留大小的计算,然后就可以类视图里浏览,按保留大小排序了。
对象的大小有两种统计方式:
- 本身大小(Shallow Size):对象本来的大小。
- 保留大小(Retained Size): 当前对象大小 + 当前对象直接或间接引用到的对象的大小总和。
看本身大小时,占大头的都是char[] ,byte[]之类的,没什么意思(用jmap -histo:live pid 看的也是本身大小)。所以需要关心的是保留大小比较大的对象,看谁在引用这些char[], byte[]。
b.类 标签
# 总结
使用JVisualVM可以监测线上JVM运行状况,对于我们解决生产环境的问题有很大的帮助。