jdk为我们提供了一些非常实用的小工具来帮助我们定位一些简单的JVM问题,这些小工具就在jdk/bin下面。不妨来分别看一下,本篇文章的工具都是使用的windows版本。
1.jps
从名字中可以大概看的出是做什么用的,了解liunx的都知道非常重要的一个命令ps——列出当前系统中的进程。同样jps是java版本的ps,列出当前系统中的java进程,下面是在我本机执行jps的结果:
C:\Users\Administrator>jps 4280 9532 Jps
前面的数字表示java的进程号,第一个是我本机eclipse的进程,第二个是jps命令本身的进程号,因为jps命令也是java实现的,Jps表示该进程的入口类。jps命令很简单没有太多要讲的。
2.jstat
jstat -gc pid 1000 10
表示以1秒的间隔打印10次gc的信息,下面是我执行jstat -gc 4280输出的gc日志:
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT 4544.0 4544.0 0.0 0.0 36736.0 3905.0 91488.0 45833.2 ? ? 39 0.660 199 38.241 38.902
S0C表示当前survivor space 0容量。Current survivor space 0 capacity (KB),同理S1C指的是survivor space 1的容量(这里是4.5M)。
S0U表示Survivor space 0 利用情况。Survivor space 0 utilization (KB),同理S1U。
EC 当前新生代eden空间容量。Current eden space capacity (KB)。这里是36M
EU 新生代eden空间利用情况。Eden space utilization (KB)。这里使用了不到4M
OC 当前年老代空间容量。Current old space capacity (KB)。这里死91M
OU 年老代利用情况。Old space utilization (KB)。这里使用了45M
PC 当前永生代空间容量。Current permanent space capacity (KB)。这里为问号,怀疑跟我本机装得jdk版本有关,我本机jdk是1.8的,1.8应该没有永久代的概念了。
PU 永生代空间利用情况。Permanent space utilization (KB)。
YGC 新生代GC事件次数。 Number of young generation GC Events。可以看到YGC发生了39次。
YGCT 新生代GC耗时。Young generation garbage collection time。39次YGC一共耗时0.66秒。
FGC full GC次数。Number of full GC events。FullGC发生了199次,full gc发生的太频繁。
FGCT full gc耗时。Full garbage collection time。199次full gc弓耗时38秒
GCT 总GC耗时。Total garbage collection time。GC总耗时将近39秒。
jstat -gccapacity pid可以看出VM堆内存三代的使用情况
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC PGCMN PGCMX PGC PC YGC FGC 13632.0 174720.0 45824.0 4544.0 4544.0 36736.0 27328.0 349568.0 91488.0 91488.0 ? ? ? ? 39 228
NGCMN表示最小新生代容量,NGCMX表示最大新生代容量,NGC表示当前的新生代容量,同理S0C,S1C,EC,OGCMN,OGCMX,OGC,OC等等。
jstat -gcutil pid可以看出当前各代的利用率和GC时间
S0 S1 E O P YGC YGCT FGC FGCT GCT 0.00 0.00 5.98 50.81 ? 39 0.660 237 45.433 46.093
比如S0的利用率为0,GC的总时间为46秒
类似的还有jstat -gccause pid比-gcutil多出两列如下:
S0 S1 E O P YGC YGCT FGC FGCT GCT LGCC GCC 0.00 0.00 6.54 50.81 ? 39 0.660 245 46.892 47.552 System.gc() No GC
多出的两列表示上一次GC的原因和本次GC的原因,可以看到LGCC原因是System.gc(),代码主动调用gc导致的,可以通过参数-XX:+DisableExplicitGC禁止显示的GC动作。
还有一些参数比如-gcnew -gcold等等不一一讲解了。
3.jinfo
jinfo用来实时查看和配置jvm的启动参数。jdk1.8版本打印出得信息非常多,就不贴出来了。另外win版本的jinfo命令有些功能并没有提供
4.jmap
通过jmap命令可以将堆转储成快照(称为heapdump或者dump),除了该命令外设置虚拟机启动参数-XX:+HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath=${目录}指定当发生OOM的时候在指定目录生成堆快照。在liunx下也可以通过kill -3命令达到同样的效果。
jmap -histo pid 打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量。
下面是eclipse进程的一步分类实例情况。
num #instances #bytes class name ---------------------------------------------- 1: 183057 19031952 [C 2: 81140 5199880 [B 3: 153279 2452464 java.lang.String
jmap -heap pid-heap 打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情况。
下面是我的eclipse的详细情况:
Attaching to process ID 4280, please wait... Debugger attached successfully. Client compiler detected. JVM version is 25.11-b03 using thread-local object allocation. Mark Sweep Compact GC Heap Configuration: MinHeapFreeRatio = 40\\GC后空闲的堆小于40%则扩大堆 MaxHeapFreeRatio = 70\\GC后空闲的堆大于70%则缩小堆 MaxHeapSize = 536870912 (512.0MB) NewSize = 13959168 (13.3125MB) MaxNewSize = 178913280 (170.625MB) OldSize = 27983872 (26.6875MB) NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 12582912 (12.0MB)\\元空间,永久代的替代者 CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 4294901760 (4095.9375MB) G1HeapRegionSize = 0 (0.0MB) Heap Usage: New Generation (Eden + 1 Survivor Space): capacity = 42270720 (40.3125MB) used = 1896728 (1.8088607788085938MB) free = 40373992 (38.503639221191406MB) 4.487096505571706% used Eden Space: capacity = 37617664 (35.875MB) used = 1896728 (1.8088607788085938MB) free = 35720936 (34.066139221191406MB) 5.04212063779399% used From Space: capacity = 4653056 (4.4375MB) used = 0 (0.0MB) free = 4653056 (4.4375MB) 0.0% used To Space: capacity = 4653056 (4.4375MB) used = 0 (0.0MB) free = 4653056 (4.4375MB) 0.0% used tenured generation: capacity = 93683712 (89.34375MB) used = 47601728 (45.39654541015625MB) free = 46081984 (43.94720458984375MB) 50.811103642007694% used 22825 interned Strings occupying 1880328 bytes.
jmap -dump:format=b,file=c pid将堆内存转储成文件c
分析jmpa转储的dump文件可以使用jdk自带的jhat工具,执行命令为jhat -J-Xmx1024M c指定分析dump文件的内存大小为 1024M文件为c,分析完会输出
Started HTTP server on port 7000
Server is ready.
我们通过浏览器输入http://localhost:7000即可看到分析的结果。
除了jhat工具还可以使用其他的一些工具来分析比如Memory Analyzer。不过一般不推荐使用自带的jhat去分析。
5.jstack
java堆栈跟踪工具,用于生成当前jvm进程的线程快照。如下是我本机eclipse的线程堆栈快照的一部分:
2015-03-07 23:17:03 Full thread dump Java HotSpot(TM) Client VM (25.11-b03 mixed mode): "Worker-61" #478 prio=5 os_prio=0 tid=0x288b2400 nid=0x18a4 in Object.wait() [0x29f1f000] java.lang.Thread.State: TIMED_WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x0ed4f5a8> (a org.eclipse.core.internal.jobs.WorkerPool) at org.eclipse.core.internal.jobs.WorkerPool.sleep(WorkerPool.java:188) - locked <0x0ed4f5a8> (a org.eclipse.core.internal.jobs.WorkerPool) at org.eclipse.core.internal.jobs.WorkerPool.startJob(WorkerPool.java:220) at org.eclipse.core.internal.jobs.Worker.run(Worker.java:50) "Worker-60" #468 prio=5 os_prio=0 tid=0x288b0400 nid=0x20c4 in Object.wait() [0x2d8df000] java.lang.Thread.State: TIMED_WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x0ed4f5a8> (a org.eclipse.core.internal.jobs.WorkerPool) at org.eclipse.core.internal.jobs.WorkerPool.sleep(WorkerPool.java:188) - locked <0x0ed4f5a8> (a org.eclipse.core.internal.jobs.WorkerPool) at org.eclipse.core.internal.jobs.WorkerPool.startJob(WorkerPool.java:220) at org.eclipse.core.internal.jobs.Worker.run(Worker.java:50)
可以看到线程名,线程号,线程的状态等信息。
6.JConsole
jconsole是jdk提供的一款图形界面的监控工具,能够展示出JVM各个方面的信息,比如当前内存,CPU的使用情况,各个堆的情况等。
主要的内容包括,概括、内存、线程、类、VM摘要、MBean这六大方面。详细的内容各位可以自己打开一个监控工具浏览一下。
7.Java VisualVM
Java VisualVM工具是目前随JDK发布的功能最强大的监视和故障处理工具,相比Jprofiler等专业的工具也毫不逊色,并且它还有一个非常大的优点就是不需要被监视的程序基于特殊Agent运行,因此它对应用程序的实际性能影响很小,可以直接应用在生产环境中。并且该工具基于插件的模式,可以安装很多功能强大的插件来增加功能。
下面是VisualVM的一个截图:
该图展示的是一个性能分析的结果,给出了热点方法和各个方法的调用次数时间比率等等。
VisualVM不仅可以连接本机的JVM进程,还可以连接远程的JVM进程。上图中“本地”展示的是本机的所有JVM进程,选择一个进程右击可以完成线程dump,进程dump,生成应用程序快照。
详细的功能和简介可以直接访问VisualVM入门指南
下面我们讲解一个插件BTrace WorkBench的使用,BTrace作用是在不停止目标程序运行的前提下,通过HotSpot虚拟机的HotSwap技术动态插入原本不存在的调试代码。比如遇到了我们的程序出问题,而又没有足够的打印语句时,我们一般的方法是不得不停掉服务,然后修改代码,增加打印语句,重新编译重新运行来解决,效率很低。BTrace其实也是java代码,只不过是多一些注解。下面是一个例子,该例子的主程序如下:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class BTraceTest { public static void main(String[] args) throws IOException { BTraceTest t = new BTraceTest(); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); for (int i = 0;i<10;i++) { br.readLine(); int a = (int)Math.round(Math.random() * 1000); int b = (int)Math.round(Math.random() * 1000); System.out.println(t.add(a, b)); } } public int add(int a,int b) { return a+b; } }
比如运行时我们想知道当前a,b和a+b的值,利用BTrace脚本可以在程序运行时得到想要的结果,首先安装BTrace WorkBench插件,然后VisualVM左侧对应的程序上右键出现Trace Application,点击即可,我们的BTrace脚本如下:
/* BTrace Script Template */ import com.sun.btrace.annotations.*; import static com.sun.btrace.BTraceUtils.*; @BTrace public class TracingScript { /* put your code here */ /*指明要查看的方法,类*/ @OnMethod(clazz="BTraceTest",method="add", [email protected](Kind.RETURN)) public static void func(int a,int b,@Return int result) { println(a); println(b); println(result);//打印调用结果 jstack();//打印调用栈 } }
当程序运行时,BTrace能够捕捉add方法的执行,并打印结果,BTrace能够实现的功能不仅仅如此,其他的功能可以上网查阅相关的资料。
上面就是对jdk自带的一些小工具的一个简要介绍。