上节学习回顾
从课本章节划分,《垃圾收集器》和《内存分配策略》这两篇随笔同属一章节,主要是从理论+实验的手段来讲解JVM的内存处理机制。好让我们对JVM运行机制有一个良好的概念,才能继续往下学习。
本节学习重点
本节主要是针对JVM内存管理机制的一些监控手段,例如堆情况使用的监控,线程栈情况的监控等。有几句废话还是有必要在这里强调的,工具是人类思维的工具,例如Java语言是人类满足需求的一种技术手段,而监控工具只是维护程序应用的一种手段。所以,思考的逻辑思维要清晰,是问题引导工具,而不是工具引导问题。
在接下来我们要学习JVM性能监控工具之前,先理清一点,这些工具的服务对象是JVM,那么这些工具肯定跟JVM的结构脱不了关系,所以熟悉JVM结构等理论知识是必要的。例如JVM的区域组成部分中有虚拟机栈、方法区、堆等,当问题发生在虚拟机栈的时候(线程阻塞),如果异常信息不能协助定位问题的情况下,那么接下来肯定是得借助监控虚拟机栈工具或监控虚拟机栈插件来协助解决问题。如果对JVM没有很好的认识,这些工具都是鸡肋。工具只是协助作用,不是解决问题的根本。
注意:下面描述的所有场景都是基于JDK1.6.0_45测试的。
虚拟机性能监控工具:JDK命令行工具
在JDK的bin目录中,有我们熟知的Java语言编程编译器命令“javac”以及执行命令“java”,此外里面还有一堆比较少用的命令,而其中有许多是JDK自带的性能监控和故障处理命令,下面逐一介绍,bin目录如下图所示:
名称 |
主要作用 |
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,显示虚拟机的线程快照 |
- jps:虚拟机进程状况工具
JDK的很多小工具的名字都参考了UNIX命令的命名方式,jps(JVM Processs Status Tool)是其中的典型,除了名字像UNIX的ps命令之外,它的功能也和ps命令类似:可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class,main()函数所在的类)名称以及这些进程的本地虚拟机唯一ID(Local Virtual Machine Identifier,LVMID)。LVMID与操作系统的进程ID是一致的。
jps命令格式:jsp [options] [hostid]
jps执行样例:
[root@WC01 bin 16:02 #18]$ jps -l
3850 org.apache.catalina.startup.Bootstrap
10579 sun.tools.jps.Jps
jps可以通过RMI协议查询开启了RMI服务的远程虚拟机进程状态,hostid为RMI注册表中注册的主机名。jps的其它常用选项请见下表:
选项 |
作用 |
-q |
只输出LVMID,省略主类的名称 |
-m |
输出虚拟机进程启动时传递给主类main()函数的参数 |
-l |
输出主类的全名,如果进程执行的是jar包,输出Jar路径 |
-v |
输出虚拟机进程启动时JVM参数 |
- jstat:虚拟机统计信息监视工具
jstat(JVM Statistics Monitoring Tool)是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,在没有GUI图形界面,只是提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。
jstat命令格式:jstat [option vmid [ interval[s|ms] [count] ] ]
对于命令格式中的VMID与LVMID需要也别说明一下:如果是本地虚拟机进程,VMID与LVMID是一致的。如果是远程虚拟机进程,那VMID的格式应当是:
[protocol:] [//] lvmid [@hostname [:port]/servername]
参数interval和count代表查询间隔和次数,如果省略这两个参数,说明只查询一次。假设需要每250毫秒查询一次进程ID为2764的垃圾收集状况,一共查询20次,那命令应当是:
jstat –gc 2764 250 20
选项option代表着用户希望查询的虚拟机信息,主要分为3类:类装载、垃圾收集、运行期编译状况,具体选项作用请参考以下表格描述:
选项 |
作用 |
-class |
监视类装载,卸载数量、总空间以及类装载所耗费的时间 |
-gc |
监视Java堆情况,包括Eden区、两个survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息 |
-gccapacity |
监视内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间 |
-gcutil |
监视内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比 |
-gccause |
与-gcutil功能一样,但是会额外输出导致上一次GC产生的原因 |
-gcnew |
监视新生代GC情况 |
-gcnewcapacity |
监视内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间 |
-gcold |
监视老年代GC状况 |
-gcoldcapacity |
监视内容与-gcold基本相同,输出主要关注使用到的最大、最小空间 |
-gcpermcapacity |
输出永久代使用到的最大、最小空间 |
-compiler |
输出JIT编译器编译过的方法、耗时等信息 |
-printcompilation |
输出已经被JIT编译的方法 |
jstat执行样例:
[root@WC01 bin 17:59 #35]$ jstat -gcutil 3850
S0 S1 E O P YGC YGCT FGC FGCT GCT
0.00 50.00 71.32 42.89 50.56 639 1.727 5 0.327 2.054
S0、S1表示Survivor1、Survivor2,分别使用了0%和50%的空间;
E表示Eden区,使用了71.32%的空间;
O表示老年代Old区,使用了42.89%的空间;
P表示永久代Pernament区,使用了50.56的空间;
YGC表示执行Minor GC的次数,一共执行了639次;
YGCT表示执行Minor GC的总耗时为1.727秒
FGC表示执行Full GC的次数,一共执行了5次;
FGCT表示执行Full GC的总耗时为0.327秒;
GCT表示所有GC总耗时为2.054秒。
- jinfo:Java配置信息工具
jinfo(Configuration Info for Java)的作用是实时地查看和调整虚拟机各项参数。
jinfo命令格式:jinfo [option] pid
其中option选项可参考一下描述:
选项 |
作用 |
-flag |
输出指定虚拟机参数,如jinfo -flag MaxHeapSize pid |
-sysprops |
输出虚拟机进程的System.getProperties()的内容 |
-flag[+|-] name |
修改虚拟机参数值 |
-flag nam=value |
同上 |
注:JDK1.6中,jinfo对于Windows平台功能仍然有较大的限制,只提供最基本的-flag选择。
jinfo执行样例:
[root@WC01 bin 18:21 #47]$ jinfo -flag MaxHeapSize 3850
-XX:MaxHeapSize=492830720
- jmap:Java内存映像工具
jmap(Memory Map for Java)命令用于生成堆转储快照(一般称为heapdump或dump文件)。如果不使用jmap命令,要想获得Java堆转储快照,还有一些比较“暴力”的手段;
1、通过-XX:+HeapDumpOnOutOfMemoryError参数,可以让虚拟机在OOM异常出现之后自动生成dump文件;
2、通过-XX:HeapDumpOnCtrlBreak参数则可以使用[Ctrl]+[Break]键让虚拟机生成dump文件;
3、可在Linux系统下通过kill -3命令发送进程退出信号“吓唬”一下虚拟机,也能拿到dump文件。
jmap命令格式:jmap [option] vmid
jmap的作用并不仅仅是为了获取dump文件,它还可以查询finalize执行队列、Java堆和永久代的详细信息,如果空间使用率、当前用的是哪种收集器等。请参考以下选项参数描述:
选项 |
作用 |
-dump |
生成Java堆转储快照。格式为:-dump:[live, ]format=b, file=<filename>,其中live子参数说明是否只dump出存活的对象 |
-finalizerinfo |
显示在F-Queue中等待Finalizer线程执行finalize方法的对象。只在Linux/Solaris平台下有效 |
-heap |
显示Java堆详细信息,如使用哪种回收器、参数配置、分代状况等。只在Linux/Solaris平台下有效 |
-histo |
显示堆中对象统计信息,包括类、实例数量、合计容量 |
-permstat |
以ClassLoader为统计口径显示永久代内存状态。只在Linux/Solaris平台下有效 |
-F |
当虚拟机进程对-dump选项没有响应时,可使用这个选项强制生成dump快照。只在Linux/Solaris平台下有效 |
jmap执行样例:
[root@WC01 dumpfile 18:49 #66]$ jmap -dump:format=b,file=tomcat.bin 3850
Dumping heap to /usr/local/apache-tomcat-6.0.43/logs/dumpfile/tomcat.bin ...
Heap dump file created
[root@WC01 dumpfile 18:49 #67]$ ls
tomcat.bin
[root@WC01 dumpfile 18:49 #68]$
- jhat:虚拟机堆转储快照分析工具
Sun JDK提供jhat(JVM Heap Analysis Tool)命令与jmap搭配使用,来分析jmap生成的堆转储快照。Jhat内置了一个微型的HTTP/HTML服务器。生产dump文件的分析结果后,可以在浏览器中查看。
jhat执行样例:
[root@WC01 dumpfile 12:35 #9]$ jhat tomcat.bin
Reading from tomcat.bin...
Dump file created Wed Jul 20 18:49:38 CST 2016
Snapshot read, resolving...
Resolving 280853 objects...
Chasing references, expect 56 dots........................................................
Eliminating duplicate references........................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
用户在浏览器中输入http://localhost:7000/就可以看到分析结果了,如下图所示:
注意:如果有其它工具可以分析,否则不建议使用jhat。首先,一般不会直接在生产环境直接分析dump文件,因为分析dump文件是一件耗时耗资源的事情,条件允许的话首选图形分析工具(后面会介绍);其次是jhat的分析功能过于简陋。
- jstack:Java堆栈跟踪工具
jstack(Stack Trace for Java)命令用于生产虚拟机当前时刻的线程快照(一般称为threaddump或者javacore文件)。线程快照就是当虚拟机内每一条线程正在执行的方法堆栈集合,生产线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致长时间等待等都是导致线程长时间停顿的常见原因。线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做些什么事情,或者等待着什么资源。
jstack命令格式:jstack [option] vmid
option选择的合法值域具有含有请看下表:
选项 |
作用 |
-F |
当正常输出的请求不被响应时,强制输出线程堆栈 |
-l |
除堆栈外,显示关于锁的附加信息 |
-m |
如果调用到本地方法的话,可以显示C/C++的堆栈 |
jstack执行样例:
[root@WC01 dumpfile 12:51 #13]$ jstack -l 3845
2016-07-21 12:51:53
Full thread dump Java HotSpot(TM) Server VM (20.45-b01 mixed mode):
"Attach Listener" daemon prio=10 tid=0x0852b000 nid=0x1103 waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
......
......
......
"main" prio=10 tid=0x08058400 nid=0xf06 runnable [0xf6940000]
java.lang.Thread.State: RUNNABLE
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:408)
- locked <0xea12b248> (a java.net.SocksSocketImpl)
at java.net.ServerSocket.implAccept(ServerSocket.java:462)
at java.net.ServerSocket.accept(ServerSocket.java:430)
at org.apache.catalina.core.StandardServer.await(StandardServer.java:430)
at org.apache.catalina.startup.Catalina.await(Catalina.java:676)
at org.apache.catalina.startup.Catalina.start(Catalina.java:628)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:289)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:414)
Locked ownable synchronizers:
- None
"VM Thread" prio=10 tid=0x080d4000 nid=0xf09 runnable
"GC task thread#0 (ParallelGC)" prio=10 tid=0x0805f400 nid=0xf07 runnable
"GC task thread#1 (ParallelGC)" prio=10 tid=0x08060c00 nid=0xf08 runnable
"VM Periodic Task Thread" prio=10 tid=0xd1978400 nid=0xf13 waiting on condition
JNI global references: 1089
- HSDIS:JIT生成代码反汇编
由于本人没有学习过汇编,所以在此章节部分的汇编内容看不懂,所以在这里也只简单介绍一下这个虚拟机插件的大概使用,后续本人学习了汇编语言后有机会再写相关的学习资料。
HSDIS是一个Sun官方推荐的HotSpot虚拟机JIT编译代码的反汇编插件。在这先插两句对JIT的简单描述,JIT是讲.class字节码翻译成本机的机器代码(就是0和1),至于为什么这么做,肯定是提高效率,更多JIT知识可自行学习。 而HSDIS就是把这些被JIT翻译过的机器码(0和1)反编译为汇编(面向IT人员的开发语言)。为什么要无端端把机器语言翻译会开发人员看得懂的汇编语言,是因为当我们需要检查程序性能的时候,希望更能接近计算机语言的本质去分析,由于汇编是直接面向硬件的,而机器语言我们又看不懂,所以最接近本质的还是汇编语言。当然,我们可以基于字节码(.class)层面上进行分析,但随着技术的发展,这些字节码指令的执行过程等细节与虚拟机规范所描述的相差越来越远,就是字节码的行为跟机器码的行为有可能差异很大。所以通过汇编语言分析可以更接近计算机语言的本质。
这个HSDIS JIT反汇编插件包含在HotSpot虚拟机的源码之中,但没有提供编译后的程序。在Project Kenai的网站也可以下载到单独的源码。它的作用是让HotSpot的-XX:PrintAssembly指令调用它来把动态生成的本地代码还原为汇编代码输出,同时还生成了大量有价值的注释,这样我们就可以通过输出代码来分析问题。读者可以根据自己的操作系统和CPU类型从Project Kenai的网站上下载编译好的插件,直接放到JDK_HOME/jre/bin/client和JDK_HOME/jre/bin/server目录中即可。如果没有找到所需操作系统的成品,那就得自己使用源码编译一下。
更多详情可以自行参考《深入理解Java虚拟机》P112部分内容。
虚拟机性能监控工具:JDK的可视化工具
JDK中除了提供大量的命令行工具外,还有两个功能强大的可视化工具:JConsole和VisualVM,这两个工具是JDK的正式成员。
- JConsole:Java监视与管理控制台
JConsole(Java Monitoring and Management Console)是一种基于JMX的可视化监视、管理工具。它管理部分的功能是针对JMX Mbean进行管理。知识扩展:JMX(Java Management Extensions)即ava管理扩展,MBean(managed beans)即被管理的Beans。一个MBean是一个被管理的Java对象,有点类似于JavaBean,一个设备、一个应用或者任何资源都可以被表示为MBean,MBean会暴露一个接口对外,这个接口可以读取或者写入一些对象中的属性。
通过JDK_HOME/bin目录下的“jconsole.exe”启动JConsole后,讲自动搜索出本机运行的所有虚拟机进程,不需要用户自己再用JPS来查询了。也可以使用下面的“远程进程”功能来连接远程服务器,对远程虚拟机进行监控,如下图所示:
从以上截图可以看出,我本地的机器运行了MyEclipse(7888)、JConsole(6084)和Tomcat(2496)三个虚拟机,而下面是我的一个远程虚拟机,需要开启JMX服务才能连接。下图为进入JConsole的主页:
“概述”页签显示的是整个虚拟机主要运行数据的概览,其中包括“堆内存使用情况”、“线程”、“类”、“CPU使用情况”4种信息的曲线图,这些曲线图是后面“内存”、“线程”、“类”页签的信息汇总。其它详细的页签都不加以说明,自行学习吧。
- VisualVM:多合一故障处理工具
VisualVM(ALL-in-One Java Troubleshooting Tool)是到目前为止随JDK发布的功能最强大的运行监控和故障处理程序,并且可以预见在未来的一段时间内都是官方主力发展的虚拟机故障处理工具。VisuaVM是基于NetBean平台开发的,因此它一开始就具备了插件扩展功能的特性,通过插件扩展支持,VisualVM几乎可以做到所欲命令行工具的功能和其它Plugins的无限可能性。
首次启动VisualVM后,读者先不必着急找应用程序进行监控,因为现在VisualVM还没有加载任何插件,虽然基本的监控、线程面板的功能主程序都以默认插件的形式提供了,但是不给VisualVM装任何扩展插件,就相当于放弃了它最精华的功能,和没有安装任何应用软件的操作系统差不多。下图为进入VisualVM的主页:
插件可以进行手工安装,在相关网站上下载*.nbm包后,点击“工具—插件—已下载”菜单,然后在弹出的对话框中指定nbm包枯井变可以进行安装,插件安装后存放在JDK_HOME/lib/visualvm/visualvm中。当然同样可以在有网络连接的环境下选择自动安装,点击“工具—插件—可用插件”弹出如下图所示的插件页签中选择合适的插件安装即可。
从VisualVM主页的左菜单栏可以看到,显示的虚拟机进程跟JConsole显示的是一样的,还有一个远程虚拟机进程。当我点击进入一个虚拟机进程后的进程主页如以下所示(不同版本可以会有所差异):
虚拟机进程主页包含了“概述”、“监视”、“抽样器”、“Visual GC”页签,其中“Visual GC”是我自己安装的插件,更详细的VisualVM使用说明就不多说了,想了解的可自行学习。
总结
其实,在学习本章知识之前,我是没有在任何系统的生成环境使用过这些JVM性能监控工具的,因为不同虚拟机的JDK分析都有自己的分析工具,况且不同企业的开发环境也不一样。比如我所开发系统的生成环境,完全与外网隔离,只能在一个密封的环境通过内网终端去连接生成环境进行维护。而且我们使用的是WAS中间件(内置IBM J9虚拟机),完全没有以上介绍的命令行工具。WAS自带了一个性能监控控制台,同样是插件化形式的可视化监控工具。关于一些异常分析,我是通过kill -3生成多个短时间间隔的dump文件通过 IBM Thread and Monitor Dump Analyzer for Java工具进行详细分析。随让工具不一样,但问题分析思路是一样的,就像我文章开头所强调的,是问题引导工具而不是工具引导问题。