经常使用适当的虚拟机监控和分析工具可以加快我们分析数据、定位解决问题的速度。这里所说的数据包括:运行日志、异常堆栈、GC 日志、线程快照(threaddump / javacore 文件)、堆转储快照(headdump / hprof 文件)等
Sun 公司(现在应该是 Oracle 了)其实为Java开发者提供了许多的免费便利工具,虽然他们在软件的使用说明上把他们声明为“没有技术支持并且是实验性质的(unsupported and experimental)的产品”,但事实上这些工具都非常的稳定且强大。如图:
大家能看到其实大部分的工具类都稳定在16-17KB左右,这其实是因为这些工具类只不过是在 tools.jar 类库的一层薄包装而已。不信我们看下 tools.jar 里都有啥:
怎么样,有没有看到我们熟悉的东西。jps jstat jmap jstack~
至于Linux上的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,显示虚拟机的线程快照(threaddump) |
为做以下实验,写了个简单的小程序来模拟正常程序运行:
public class JVMTools {
public JVMTools() {
System.out.println(System.currentTimeMillis() + " A New Object (" + this.toString() + ") Created!");
}
public static final int _1MB = 1024 * 1024;
// 老规矩,创建一个占点内存的对象,好观测
private byte[] arr = new byte[2 * _1MB];
private int a = 0;
public void add() throws InterruptedException {
for (int i = 0; i < 100; i++) {
a ++;
Thread.sleep(100);
}
System.out.println(System.currentTimeMillis() + " Object (" + this.toString() + ") execute add method end!");
}
public static void main(String[] args) throws InterruptedException {
while (true) {
new Thread(() -> {
JVMTools tmp = new JVMTools();
try {
// 保证他能存活10秒钟
tmp.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
Thread.sleep(1000);
}
}
}
大体意思就是每秒创建一个对象,对象的存活时间为10秒,也就是说理论上内存中总有10个存活对象,用来观察内存的变化。
jps 的作用就是列出当前在运行的 JVM,查出的 LVMID 跟操作系统中的 PID (进程ID)是一致的,
jps 的命令格式如下:
jps [options] [hostid] 例如 jps -l
jps 可以通过 RMI协议查询开启了 RMI 服务器的远程虚拟机进程状态,命令中的 hostid 为 RMI 注册表中注册的主机名。jps其他常用选项看下表:
选项 | 作用 |
---|---|
-q | 只输出 LVMID ,省略主类的名称 |
-m | 输出虚拟机进程启动时传递给主类 main() 函数的参数 |
-l | 输出主类的全名,如果进城执行的是 Jar 包,则输出包路径 |
-v | 输出虚拟机进程启动时的 JVM 参数 |
例如:
4.2.2 jstat:虚拟机统计信息监视工具
jstat(JVM Statistics Monitoring Tool)是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据,在没有 GUI 图形界面,只提供纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。
jstat 命令格式为:
jstat [ option vmid [ interval [ s | ms ] [count] ] ] 例如 jstat -gcutil 20028 100 25
具体选项如下:
选项 | 作用 |
---|---|
-class | 监视类装载、卸载数量、总空间及类装载所耗费的时间 |
-gc | 监视Java堆状况,包括 Eden 区、2个 survivor 区、老年代、永久代等的容量,已用空间、 GC 时间合计等信息 |
-gccapacity | 监视内容与 -gc 基本相同,但输出主要关注 Java 堆各个区域使用到的最大和最小空间 |
-gcuil | 监视内容与 -gc 基本相同,但输出主要关注已使用空间占总空间的百分比 |
-gccause | 与 -gcutil 功能一样,但是会额外输出导致上一次 GC 产生的原因 |
-gcnew | 监视新生代 GC 的状况 |
-gcnewcapacity | 监视内容与 -gcnew 基本相同,输出主要关注使用到的最大和最小空间 |
-gcold | 监视老年代 GC 的状况 |
-gcoldcapacity | 监视内容与 -gcold 基本相同,输出主要关注使用到的最大和最小空间 |
-compiler | 输出JIT编译器编译过的方法,耗时等信息 |
-printcompilation | 输出已经被JIT编译的方法 |
这些选项太多,不一一实验,仅以最简单的为例:
这截图代表的意思是 针对 20028 每100 毫秒 记录一次,共记录 25 次GC信息,
字段含义如下:
其余字段都好理解,这个CCS可能会难懂一些,不过当你仔细研读了 JDK8 中的Meta Space 后就知道了。这边我简单讲下,参考博客如下:
CCS 全名为 Compressed Class Pointer Space(类指针压缩空间),它实际上是 JDK 8 中元数据区的一部分。前两天的博客中有提到过方法区原来存在永久代中,后来放到了元空间,每个对象实例中都有一个指针指向自己的类信息,也就是我们说的 klass ,那么这个指针就是 _klass 指针,那这个指针在不同的虚拟机中所占的长度也不同。例如 32 位虚拟机中,_klass 指针占 4 字节,但是 64 位虚拟机中 _klass 指针就占了 8 字节。为了压缩对象中的 _klass 指针的大小,引入了 CCS 这么个区域,就是说只有是64位平台上启用了类指针压缩才会存在这个区域。CCS 中只包含类的元数据,剩下的信息还是放在 Meta Space 中。这里这个CCS 字段,实际上指的是 CCS 的已使用率,具体算法是 :
(1-((sun.gc.compressedclassspace.capacity - sun.gc.compressedclassspace.used)/sun.gc.compressedclassspace.capacity)) * 100
capacity 代表的是 CCS 的总容量(在内存中已经 Commit的)大小,used 则代表已经使用的。
在截图中我们可以明显的看到因为新对象的创建导致的 Eden 区使用率上涨。
jinfo 可以用来查看正在运行的 JVM 的各项参数,它还可以通过 -sysprops 参数打印出虚拟机进程的 System.getProperties() 的内容。它也可以通过 -flag [ +|- ] name 或 -flag name=value 两种方式动态的修改正在运行的 JVM 一些参数。当系统崩溃时,jinfo可以从core文件里面知道崩溃的Java应用程序的配置信息。实例如下:
jinfo -sysprops 20028
jinfo -flags 20028 :
jmap 就是我们常常用来做 heapdump 的主要工具,虽然可以用其他方式(例如 加一些 JVM 启动参数)来生成,但是 jmap 仍然是最方便的一种。话不多说上用法(不弄表格了,懒了懒了):
Usage:
jmap [option] <pid>
(to connect to running process)
jmap [option] <executable <core>
(to connect to a core file)
jmap [option] [server_id@]<remote server IP or hostname>
(to connect to remote debug server)
where <option> is one of:
<none> to print same info as Solaris pmap
-heap to print java heap summary
-histo[:live] to print histogram of java object heap; if the "live"
suboption is specified, only count live objects
-clstats to print class loader statistics
-finalizerinfo to print information on objects awaiting finalization
-dump:<dump-options> to dump java heap in hprof binary format
dump-options:
live dump only live objects; if not specified,
all objects in the heap are dumped.
format=b binary format
file=<file> dump heap to <file>
Example: jmap -dump:live,format=b,file=heap.bin <pid>
-F force. Use with -dump:<dump-options> <pid> or -histo
to force a heap dump or histogram when <pid> does not
respond. The "live" suboption is not supported
in this mode.
-h | -help to print this help message
-J<flag> to pass <flag> directly to the runtime system
不做表格详细说明了,用烂了都。话不多说上例子:
jmap -dump:live,format=b,file=Demo.hprof 20028
JDK 自身提供一种分析dump文件的工具—— jhat(JVM Heap Analysis Tool)命令与 jmap 搭配使用。但是实话说一般人都不用,太难用了。都不如 VisualVM,用的最多的应该还要数 Eclipse Memory Analyzer 和 IBM HeapAnalyzer 两大工具了。不管怎样,写都写了,就展示一下吧:
待出现Server is ready字样,输入http://localhost:7000/ 即可查看
jstack(Stack Track for Java)命令,可以生成当前虚拟机当前时刻的线程快照(threaddump/Javacore)。用以分析是否发生死锁,线程为何反应时间过长等问题。可以看到详细的java栈信息,老规矩不上表格了:
Usage:
jstack [-l] <pid>
(to connect to running process)
jstack -F [-m] [-l] <pid>
(to connect to a hung process)
jstack [-m] [-l] <executable> <core>
(to connect to a core file)
jstack [-m] [-l] [server_id@]<remote server IP or hostname>
(to connect to a remote debug server)
Options:
-F to force a thread dump. Use when jstack <pid> does not respond (process is hung)
-m to print both java and native frames (mixed mode)
-l long listing. Prints additional information about locks
-h or -help to print this help message
同样是用烂了的命令,话不多说上例子:
# 直接查看栈信息
jstack -l 20028
# 将栈信息保存在 threaddump.log 文件中
jstack -l 20028 >> threaddump.log
这里还能把文件保存下来,为什么要保存呢。因为有这个好东西:
https://www.fastthread.io/
这个网站可以分析你上传的dump文件,帮助快速定位。示例如下:
多余的图就不放了,大家感兴趣的自行探索~
JDK 中实际上提供了两种可视化工具,而这两种工具并未被贴上 unsupported and experimental 的标签,那就是 Jconsole 和 VisualVM 。
启动
通过目录下的jconsole.exe 启动后,可以直接选择线程而无需使用 jps 再去查
连接上之后,就可以很直观的看到概览、内存、线程、类等趋势图:
书中针对工具有不同的例子作解释,此处不再展开说。有兴趣的童鞋可以参考原书自行研究~
VisualVM 无法直接启动,是需要通过插件启动的。比如我使用的 IntelliJIDEA2019.2.3 。启动的时候带有插件:
程序启动后会随之将 VisualVM 一并启动。VisualVM 启动后会出现如下图所示概览,书中对软件使用方法做了详细描述,本文不再赘述,仅挑部分功能截图。
本章主要是讲解一些随JDK的实用小工具,用时再回来翻阅即可
本文仅是在自我学习 《深入理解Java虚拟机》这本书后进行的自我总结,有错欢迎友善指正。
欢迎友善交流,不喜勿喷~
Hope can help~