虚拟机性能监控和处理工具
总阅读次
虚拟机监控和处理工具
虚拟机的处理工具主要包括JDK命令行工具和JDK可视化工具,
JDK命令行工具
Jdk命令行工具都在JAVA_HOM/bin
目录下,具体如下:
通过上图可以看出每个工具的大小都不是很大,其实它们只是JAVA_HOME/lib/tools.jar
类库的一个包装而已,在tools.jar中都包含这些命令的实现代码,tools.jar不属于Java的标准API,如下图:
下面介绍一下主要的命令行工具:
主要作用 | 名称 |
---|---|
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 Brower,用于分析headdump文件,它会建立一个HTTP/HTML服务器,让用户可以在浏览器上查看分析结果 |
jstack | Stack Trace for Java,显示虚拟机的线程快照 |
下面将详细介绍每一个命令:
jps:虚拟机进程状况工具
该命令类似于Linux的ps命令,作用也差不多:可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class ,main()方法所在的类)名称以及这些进程的本地虚拟机唯一ID(Local Virtual Machine Identifier,LVMID)。虽然功能比较单一,但是使用频率最高的JDK命令,因为很多命令的操作都是基于LVMID的,并且会显示主类名很多时候都是通过主类名进行区分的。
jps命令格式:
jps [options][hostid]
jsp执行样例:
远程执行样例:
jps命令各个选项的含义
选项 | 作用 |
---|---|
-q | 只输出LVMID,省略主类的名称 |
-m | 输出虚拟机进程启动时传递给主类main()函数的参数 |
-l | 输出主类的全名,如果进程执行的是jar包,输出jar路径 |
-v | 输出JVM启动时JVM参数 |
jstat:虚拟机统计信息监工具
jstat(JVM Statistics Monitoring Tool)是用于监视虚拟机各种状态信息的命令行工具。它可以显示本地或者远程虚拟机的类加载、内存、垃圾收集、JIT编译等运行数据。下面来一一展现(这里使用的是Tomcat的实例测试的,VM参数上面演示JPS的时候已经给出):
命令格式:jstat [option vmid [interval[s|ms] [count]]]
jstat -gc 2176 1000 10 每1秒钟对进程2176进行一次垃圾收集查询,共查询10次
通过上图可以看出每个区的使用情况和Minor GC、Full GC次数和时间、总GC时间,详细如下
|
|
jstat -gcutil 2176 1000 10 输出每个区占总空间的百分比,每一秒钟输出一次总共输出10次
|
|
下面介绍jstat命令都有哪些option
选 项 | 作 用 |
---|---|
-class | 监视类装载、卸载数量、总空间以及装载所消耗的时间 |
-gc | 监视Java堆状况,包括Eden区、两个Survivor区、老年代、永久代等的容量,GC次数、时间合计 |
-gccapacity | 监视内容与-gc 基本相同,但是输出关注Java堆各个区域使用到的最大、最小空间 |
-gcutil | 监视内容与-gc 基本相同,但是输出关注Java堆各个区域所占百分比 |
-gccause | 与-gcutil 功能一样,但是会额外输出导致上一次GC产生的原因 |
-gcnew | 监视新生代GC状况 |
-gcnewcapacity | 监视内容与-gcnew 基本相同,输出主要关注使用最大、最小空间 |
-gcold | 监视老年代GC状况 |
-gcoldcapacity | 监视内容与-gcold 输出主要关注使用最大、最小空间 |
-gcpermcapacity | 输出永久代使用到的最大、最小空间 |
-compiler | 输出JIT编译器编译过的方法、耗时等信息 |
-printcompilation | 输出已经被JIT编译好的方法 |
jinfo:Java配置信息工具
jinfo(Configuration Info for Java)的作用是实时地查看和调整虚拟机各项参数。可以使用-sysprops
选项把虚拟机进程的System.getProperties()的内容打印出来。改命令在JDK5时,只能在Linux使用;JDK6后可以在Windows使用。
jinfo命令格式:
jinfo [option] <pid>
pid就不用介绍了,option介绍一下:
-flag <name>
表示输出对应VM flag name的值
-flag [+|-]<name>
表示设置或者取消指定VM flag name
-flag <name>=<value>
表示设置给定的值到指定VM flag name的值
-flags
表示输出所有的VM flags
-sysprops
表示输出Java的系统属性
演示案例如下:
jinfo -flags
jinfo -flag PrintGCDetails
查看打印GC详细信息当前值
动态取消打印GC详情信息
动态设置打印GC详细信息
jmap:Java内存映像工具
jmap(Memory Map for Java)命令用于生成堆转储快照(一般称为headdump或dump文件)。jmap的作用并不仅仅是为了获取dump文件,它还可以查询finalize执行队列、Java堆和永久代的详细信息。但是在Windows平台下都是受限的。
jmap命令格式:
-heap
输出java堆的信息
-histo
输出java堆对象的直方图,如果指定了live
参数则只统计存活对象的
-permstat
输出永久代的信息
-finalizerinfo
输出等待进行finalization的对象的信息
各个option
的详细信息:
选择 | 作用 |
---|---|
-dump | 生成Java堆转存快照。格式为:-dump:[live,] format=b file=<filename> |
-finalizerinfo | 显示等待Finalizer线程执行finalize方法的对象,只在Linux/Solaris平台下有效 |
-heap | 显示Java堆详细信息,如使用哪种回收器、参数配置、分代状况等。只在Linux/Solaris平台有效 |
-histo | 显示堆中对象统计信息,包括类、实例数量、合计容量 |
-permstat | 以ClassLoader为统计口径显示永久代内存状态。只在Linux/Solaris平台下有效 |
-F | 当虚拟机进程对-dump选项没有响应时,可使用这个选项强制生成dump快照。只在Linux/Solaris平台有效 |
演示案例:
-dump
-finalizerinfo
-heap
-histo
-permstat
jhat:虚拟机堆转储快照分析工具
JDK提供jhat(JVM Heap Analysis Tool)命令与jmap搭配使用,来分析jmap生成的堆转储快照。jhat内置了一个微型HTTP服务器,可以通过浏览器查看分析结果。不过在实际情况中一般不会在生成环境的机器上进行分析,而且该命令分析的结果对于我们后面介绍的VisualVM工具分析出来的太过于简单。具体使用如下:
访问本机的7000端口就可以查看到分析结果
分析结果默认是以包为单位显示,分析内存泄漏问题主要会使用到其中的”Heap Histogram”与OQL(类似于SQL)页签的功能,前者可以找到内存中总容量最大的对象,后者是标准的对象查询语句。
jstack:Java堆栈跟踪工具
jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照;线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈集合,生成快照的目的是定位出现长时间停顿的原因,如线程死锁、死循环、请求外部资源导致长时间等待等。
jstack命令格式
jstack [option] vmid
-F,当正常输出的请求不被响应时,强制输出线程堆栈
-l,除堆栈外,显示关于锁的附加信息
-m,如果调用到本地方法的话,可以显示C/C++的堆栈
使用示例如下:
也可以通过代码的方式获取线程相关的信息:
在JDK1.5中,java.lang.Thread
类新增了一个getAllStackTraces
()方法用于获取虚拟机中所有线程的StackTraceElement对象,使用该对象可以实现通过浏览器查看线程堆栈:
|
|
JDK可视化工具
JConsole:Java 监视与管理控制台
JConsole是一种基于JMX的可视化监视、管理工具。
启动JConsole
通过JDK/bin目录下的
jconsole.exe
启动JConsole后,将自动搜索本机运行的所有虚拟机进程。具体如下图:
内存监控
“内存”页签相当于可视化的
jstat
命令,用于监视收集器管理JVM内存的变化趋势,通过如下代码来实验一下:123456789101112131415161718192021public class MonitoringTest {static class OOMObject {public byte[] placeHolder = new byte[64 * 1024];}public static void fillHeap(int num) throws InterruptedException {ArrayList<OOMObject> oomObjects = new ArrayList<>();for (int i = 0; i < num; i++) {Thread.sleep(50);oomObjects.add(new OOMObject());}System.gc();}public static void main(String[] args) throws Exception {int read = System.in.read();System.out.println(read);fillHeap(1000);}}该程序的启动参数,
-Xms100m -Xmx100m -XX:+UseSerialGC
。这段代码很简单就是以64kb/50毫秒的速度向Java堆填冲数据,一共填冲1000次;我们通过JConsole进行内存监控并和jstat
命令对比下图是通过命令
jstat -gc 10324 250 500
输出的部分结果:通过红色的竖框可以看出,eden区空间的增加是有规律的,每次增加320k。这是因为命令是250毫秒输出一次GC信息,程序正好增加了320k的内存。红色的横框表示发生了一次YGC有部分数据进入S1和老年代。下面来看JConsole通过图形的表现:
通过图可以看出Eden区的运行趋势呈现折线状,整个趋势也大致符合
jstat
命令监控出来的数据线程监控
在JConsole工具的“线程”页签可以查看线程的信息,类似
jstack
命令,监控线程长时间停顿的组要原因:等待外部资源(网络连接)、死循环、锁等待(死锁和活锁)。下面通过代码来:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647public class ThreadMonitoring {/*** 死循环演示*/public static void createBusyThread() {Thread thread = new Thread(new Runnable() {public void run() {while (true);}}, "testBusyThread");thread.start();}/*** 线程锁等待* @param lock*/public static void createLockThread(final Object lock) {Thread thread = new Thread(new Runnable() {public void run() {synchronized (lock){try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}},"testLockThread");thread.start();}public static void main(String[] args) throws IOException {BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));bufferedReader.readLine();createBusyThread();bufferedReader.readLine();Object obj = new Object();createLockThread(obj);}}程序运行后进入JConsole界面,点击“线程”选项页,如下:
可以看到main线程在等待read流;我们在程序运行控制输入字符回车,这个时候线程
testBusyThread
开始运行,如下该线程一值在运行,并且运行的代码在ThreadMonitoring.java的20行,main线程又是阻塞状态。再次在控制台输入字符,继续程序的执行,结果如下:
可以看出main线程已经结束,testLockThread
线程是WAITING状态,是在ThreadMonitoring.java的37行,等待lock对象执行notify或notifyAll方法,testBusyThread2
线程还是一直处于运行状态。
死锁演示:
代码如下:
|
|
代码是开200个线程去分别计算1+2以及2+1的值。这里使用了Integer.valueOf()
方法,该方法是基于减少对象创建次数和节省内存的考虑,[-128,127]之间的数字会被缓存,当valueOf()
方法传入参数在该范围之内,将直接返回缓存中的对象,也就是循环200次,只公用两个对象;确实也出现了死锁,如下:
通过上图可以看出出现了死锁,而且不同线程在等待其他线程的资源,导致线程卡住,不存在等待锁被释放的希望
VisualVM:多合一故障处理工具
VisualVM是目前为止随JDK发布的功能最强大的运行监控处理程序。官方文档介绍VisualVM的软件说明中写上了“All in One”,
插件安装
点击菜单,工具->插件,有可用插件和已安装插件,具体如下:
插件中心网址是:
https://visualvm.github.io/
生成、浏览堆转存快照
在VisualVM中生成dump文件有两种方式:
- 在”Applications”窗口中右键点击应用程序节点,然后选择“堆dump”
- 在”Applications”窗口中双击应用程序节点以打开应用程序标签,然后在“监视”标签中点击“堆dump”
分析程序性能
在Profiler页签中,VisualVM提供了程序运行期间方法级的CPU执行时间分析以及内存分析
BTrace动态日志跟踪
BTrace是一个VisualVM插件,本身也是可以独立运行的程序,这是官网https://github.com/btraceio/btrace。它的作用是在不停止目标程序运行的前提下,通过HotSopt虚拟机的HotSwap技术动态加入原本并不存在的调试代码。具体演示如下:
1234567891011121314151617public class BTraceTest {public int add(int a, int b) {return a + b;}public static void main(String[] args) throws IOException {BTraceTest bTraceTest = new BTraceTest();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));for(int i=0;i<10;i++){bufferedReader.readLine();int a = (int) Math.round(Math.random()*1000);int b = (int) Math.round(Math.random()*1000);System.out.println(bTraceTest.add(a,b));}}}程序运行后,在VisualVM中打开该程序的监视,在BTree页签填充如下代码:
123456789101112131415161718192021/* BTrace Script Template */import com.sun.btrace.annotations.*;import static com.sun.btrace.BTraceUtils.*;public class TracingScript {/* put your code here */(clazz="com.knight.jvm.BTraceTest",method="add",location=(Kind.RETURN))public static void func(@Self com.knight.jvm.BTraceTest instance,int a,int b,@Return int result){println("调用堆栈:");jstack();println(strcat("方法参数A:",str(a)));println(strcat("方法参数b:",str(b)));println(strcat("方法结果:",str(result)));}}
点击“Start”按钮进行编译,出现BTrace up&running
的字样,具体如下图:
运行效果图,触发程序执行和BTrace的监控代码:
配置RMI方式远程监控JVM
RMI(远程方法调用),基于该协议可以实现远程监控JVM。jstatd
命令是一个RMI的Server应用,用于监控JVM的创建和结束,并且提供接口让监控工具连接到本机的JVM。jstatd
命令位于JAVA_HOME/bin
目录下,具体使用方法如下:
在文件vi JAVA_HOME/jre/lib/security/java.policy
末尾的};
前添加permission java.security.AllPermission;
启动jstatd,进入JAVA_HOME/bin
目录下 ./jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=本机ip &
查看监听端口,默认是1099,还会监听一个随机端口,如下:
关闭防火墙
测试,在远程机器上进行测试,执行最简单的jps
命令,如下图访问到了远程的java进程信息