实战java虚拟机
jdk开发包中,除了比较熟悉的java.exe,javac.exe,还有一系列的辅助工具,它们都存放在jdk安装目录/bin目录下,乍一看这些都是exe,但实际上它们只是将java程序的一层包装,真正的实现是在lib/tools.jar中。以jps命令为例,它实质上是运行:
java -classpath $JAVA_HOME/lib/tools.jar sun.tools.jps.Jps
jps类似于ps命令,不同的是它只列出系统中所有java应用程序,jps命令格式
jps [-q] [-mlvV] [
jstat用于观察java应用程序运行时相关信息的工具。它的功能特别,它的基本语法:
jstat -
-class
Loaded Bytes Unloaded Bytes Time
1546 2835.7 0 0.0 2.06
//loaded 载入类的数量
//Bytes 载入类的合计大小
//Unloaded 卸载类的数量
//第二个bytes 卸载类的合计大小
//time 加载和卸载的时间之和
-compiler
Compiled Failed Invalid Time FailedType FailedMethod
847 0 0 1.28 0
//Compiled 编译任务执行的次数,
//Failed 编译失败的次数,
//Invalid 编译不可用次数
//FailedType 最后一次失败的类型
//FailedMethod 最后一次失败的类名和方法名
-gc
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
5120.0 5120.0 2208.0 0.0 63488.0 54658.8 84992.0 24.0 8832.0 8388.6 1152.0 984.5 4 0.021 0 0.000 0.021
//S01,S1C,S0U,S1U from,to的大小和已用量大小(KB)
//EC,EU eden的大小和已用量
//OC,OU 老边代的大小和已用量
//PC,PM 永久区大小和已用量
--- JDK8之后变为
// MC,MU ,元数据区大小和已用量
//CCSC,CCSU 压缩类空间大小和已用量
---
//YGC,YGCT: 新生代GC次数和耗时
//FGC,FGCT: Full GC次数和耗时
//GCT GC总耗时
-gccapacity
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC
41984.0 673792.0 73728.0 5120.0 5120.0 63488.0 84992.0 1347584.0 84992.0 84992.0 0.0 1056768.0 8832.0 0.0 1048576.0 1152.0 4 0
//NGCMN ,NGCMX : 新生代最小值和最大值(KB)
//MN:表示最小 ,MX表示最大
-gccause
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT LGCC GCC
42.81 0.00 86.20 0.05 95.00 85.50 4 0.018 0 0.000 0.018 Allocation Failure No GC
//LGCC 上次GC原因
//GCC 本次GC原因
-gcnew
S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT
5120.0 5120.0 2208.0 0.0 7 15 5120.0 63488.0 54658.8 4 0.021
//TT 新生代晋升到老年代年龄
//MTT 新生代晋升到老年代最大年龄
//DSS 所需的survivor区大小
jinfo可以用来查看正在运行的java应用程序的扩展参数,甚至支持在运行时,修改部分参数,它的基本语法为:jinfo [option]
它的option值可以为:
- flags
: 如-XX:NewSize,-XX:OldSize等虚拟机相关参数.
- sysprops
:java.class.path,java.vm.info等…
jmap命令是一个多功能命令。它可以生成Java程序的堆Dump文件,也可以查看堆内对象实例的统计信息、查看ClassLoader的信息以及finalizer队列。它的命令格式:
jmap [option]
(to connect to running process)
jmap [option]
(to connect to a core file)
jmap [option] [server_id@]
(to connect to remote debug server)
option选项有如下:
-histo
统计9336号进程的存活对象统计信息,并保存到文件:$ jmap -histo:live 9336 >d:/dum02.txt
,结果如下图:
,结果中统计了内存中实例对象数和合计。
-dump
-dump功能得到java程序的当前堆快照:jmap -dump:format=b,file=d:/heamp.hprof 8996
-clstats 与 permstat
该命令还可以查看系统的classloader信息,以及classloader的父子关系,以及各个classloader内部加载的类的数量和总大小。$ jmap -clstats 7876
-finalizerinfo
可以观察系统中finalizer队列中的对象,一个不恰当的finalize()方法可能导致对象堆积在finalizer队列里。$ jmap -finalizerinfo 7876
,上图的finalizer队列长度为0。
使用jhat工具可以分析java应用程序的堆快照内容:$ jhat d:/heap.hprof
,分析的结果通过浏览器 localhost:7000查看。
jstack可以打印出java程序的线程堆栈信息。jstack [option]
option选项信息:
下例为一个死锁程序,java程序代码如下:
public class DeadLock extends Thread{
protected Object myDirect;
static ReentrantLock south = new ReentrantLock();
static ReentrantLock north = new ReentrantLock();
public DeadLock(Object obj) {
this.myDirect = obj;
if(myDirect == south) {
this.setName("south");
}
if(myDirect == north) {
this.setName("north");
}
}
@Override
public void run() {
if(myDirect == south) {
try {
north.lockInterruptibly();
Thread.sleep(500);
south.lockInterruptibly();
System.out.println("car to south has passed");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("car to south is killed");
} finally {
if(north.isHeldByCurrentThread()) {
north.unlock();
}
if(south.isHeldByCurrentThread()) {
south.unlock();
}
}
}
if(myDirect == north) {
try {
south.lockInterruptibly();
Thread.sleep(500);
north.lockInterruptibly();
System.out.println("car to north has passed");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("car to north is killed");
} finally {
if(south.isHeldByCurrentThread()) {
south.unlock();
}
if(north.isHeldByCurrentThread()) {
north.unlock();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
DeadLock car2south = new DeadLock(south);
DeadLock car2north = new DeadLock(north);
car2south.start();
car2north.start();
Thread.sleep(1000);
}
}
使用命令jstak -l pid >d:/dead.txt
打印结果:
"north" prio=6 tid=0x00000000069ad000 nid=0x2bcc waiting on condition [0x000000000792f000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000007d6e54508> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:811)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:867)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1201)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:312)
at cn.jhs.chap05.DeadLock.run(DeadLock.java:50)
Locked ownable synchronizers:
- <0x00000007d6e544d8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
"south" prio=6 tid=0x000000000699c800 nid=0x1ed0 waiting on condition [0x000000000782f000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000007d6e544d8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:811)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:867)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1201)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:312)
at cn.jhs.chap05.DeadLock.run(DeadLock.java:31)
Locked ownable synchronizers:
- <0x00000007d6e54508> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
//省略输出。。。。。
Found one Java-level deadlock:
=============================
"north":
waiting for ownable synchronizer 0x00000007d6e54508, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "south"
"south":
waiting for ownable synchronizer 0x00000007d6e544d8, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "north"
Java stack information for the threads listed above:
===================================================
//省略输出。。。。。
Found 1 deadlock.
一些监控工具支持远程计算机的控制(如jps,jstat),为了开启远程监控,则需要配合使用jstatd工具。
命令jstatd则是一个RMI服务端程序,它的作用相当于对代理服务器,建立本地计算机与远程监控工具的通信。jstatd服务器将本机的java应用程序传递到远程计算机。如图:
它的命令格式:usage: jstatd [-nr] [-p port] [-n rminame]
option选项:
直接运行jstatd命令会报错,这是因为没有足够的权限所致。可以使用java的安全策略,为其分配相应的权限,并命名为all.policy
grant codebase "file:${JAVA_HOME}\lib\tools.jar" {
permission java.security.AllPermission;
};
使用命令:jstatd -J-Djava.security.policy=all.policy
服务即开启,默认端口1099。jstatd更为详尽的配置
使用jsp.jstat命令即可访问远程服务器的信息。
jps localhost:1099;
jstat -gcutil 460@localhost:1099 //----460为进程号
在jdk1.7之后,新增了一个命令行工具jcmd,它是一个多功能工具,用它可以导出堆,查看java进程,导出线程信息,执行GC等。
使用命令:jcmd -l
列出本机所有的虚拟机。
针对每一个虚拟机可以使用:jcmd pid help
来列出pid对应虚拟机所支持的命令。
然后挑选出虚拟机支持的任意命令执行:jcmd pid choosed_vm_cmd
;
也可是使用:jcmd Mainclass
来替代jcmd pid
;
虚拟机信息如下:
9196 org.jetbrains.idea.maven.server.RemoteMavenServer
======================================================
$ jcmd 9196 help
等价于
$ jcmd org.jetbrains.idea.maven.server.RemoteMavenServer help
jcmd拥有jmap大部分功能,并且Oracle官方也推荐使用jcmd替代jmap.
HPROF: Heap and CPU Profiling Agent (JVMTI Demonstration Code)
与之前的监控工具不同,hprof不是独立的监控工具,它是一个java agent工具。它可以用于监控java应用程序在运行时的CPU和堆信息。使用java agentlib:hprof=help
命令可以查看hprof的帮助文档。
Option Name and Value Description Default
--------------------- ----------- -------
heap=dump|sites|all heap profiling all
cpu=samples|times|old CPU usage off
monitor=y|n monitor contention n
format=a|b text(txt) or binary output a
file= write data to file java.hprof[{.txt}]
net=: send data over a socket off
depth= stack trace depth 4
interval= sample interval in ms 10
cutoff= output cutoff point 0.0001
lineno=y|n line number in traces? y
thread=y|n thread in traces? n
doe=y|n dump on exit? y
msa=y|n Solaris micro state accounting n
force=y|n force output to y
verbose=y|n print messages about dumps y
java -agentlib:hprof=cpu=times,interval=10 xxx
times选项记录java函数调用前后记录的时间,进而计算函数的执行时间。在当前程序目录下会生成java.hprof.txt文件,记录了性能统计信息。java -agentlib:hprof=heap=dump,format=b,file=d:/x.hprof xx
将应用程序的堆快照保存在指定文件。java -agentlib:hprof=heap=sites xxx
可以输出java应用程序中每个跟踪点上的类所占内存的百分比JConsole是JDK自带的图形化性能监控工具,通过它可以监控堆信息、永久区信息、类加载信息、线程信息、JVM信息等。
连接JAVA程序
JConsole在JAVA_HOME/bin目录下,启动后,会出现新建连接对话框,可以连接本地应用程序,也可以连接远程程序。针对远程程序,需要远程程序在启动时,增加如下参数:
-Djava.rmi.server.hostname=127.0.0.1 //---ipv4地址是:192.1.217.111
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8889
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
visual vm是一个功能强大的多合一的故障诊断和性能监控的可视化工具,使用visual vm可以代替jstat,jmap,jhat,jstack甚至替代jconsole.
使用命令:jvisualvm
启动Visual VM.
连接应用程序
Visual VM支持多种方式连接应用程序:
监控应用程序
选中了应用程序之后,即可看到监控的页面如下图:
Visual VM 的 BTrace插件(略)
BTrace是一款非常有意思的工具,它可以在不停机的情况下,通过字节码注入动态的监控系统的运行情况,它可以跟踪方法的调用,构造函数调用和系统内存等信息。
在Oracle收购sun之前,Oracle的JRockit虚拟机提供了一款JRockit Mission Control的虚拟机诊断工具。在收购sun之后,Oracle拥有了Sun Hotspot 和JRockit两款虚拟机,根据Oracle的战略,在JDK7 Update 40之后,将MissionControl集成到了Hotspot中。它位于$JAVA_HOME/bin/jmc.exe.
飞行记录器(Flight Recorder)
它通过记录程序在一段时间内的运行情况,将记录结果进行分析和展示,可以进一步对系统的性能进行分析和诊断。要使用飞行记录器,对于要监控的程序必须添加参数:
-XX:+UnlockCommercialFeatures -XX:+FlightRecorder