1. JDK的命令行工具
1.1 jps:虚拟机进程状况工具
jps(JVM Process Status Tool)可以列出正在运行的虚拟机进程,并显示虚拟机执行主类名称以及这些进程的本地虚拟机唯一ID。是使用频率最高的JDK命令行工具,因为其他的JDK工具大多需要输入它查询到的LVMID来确定要监控的是哪一个虚拟机进程。对于本地虚拟机进程来说,LVMID与操作系统的进程ID是一直的,使用任务管理器也能查询到虚拟机进程的LVMID,但是如果启动了多个虚拟机进程,无法根据进程名称定位时,就只能依赖jps命令来显示主类的功能才能区分了。
下面是我在linux上执行 jps -l 的例子,可以看到tomcat服务器的信息:
[root@AY131228205040795d8cZ bin]# jps -l
7862 org.apache.catalina.startup.Bootstrap
1356 sun.tools.jps.Jps
-q 只输出LVMID,省略主类的名称
-m 输出虚拟机进程启动时传递给主类main()函数的参数
-l 输出主类的全名,如果进城执行的Jar包,则输出Jar路径
-v 输出虚拟机进程启动时JVM参数
1.2 jstat:虚拟机统计信息监视工具
jstat(JVM Statistics Monitoring Tool) 是用于监视虚拟机各种运行状态信息的命令行工具,它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。
这里使用jps查出的 7862 LVMID,看看服务器的内存状况:
[root@AY131228205040795d8cZ bin]# jstat -gcutil 7862
S0 S1 E O P YGC YGCT FGC FGCT GCT
5.17 0.00 49.73 99.81 82.29 716 7.960 10 4.083 12.044
查询的各结果意思是:S0,S1表示Survivor0、Survivor1,里边Survivor1使用了5.17%,Survivor2为空;E表示Eden区,使用了49.73%的空间;O表示OLD老年代,使用了99.81%的空间(。。。这里说明程序有大问题了,可能是tomcat上运行了三个同样的工程的原因,之后在虚拟机的学习中来进行调优),P表示永久代,使用了82.29%的空间。后边的数据是时间单位的,YGC就是MinorGC,总共716次,耗时7.96秒;FGC当然是FullGC,共进行了10次,耗时4.083s;GCT是所有GC总耗时,前二者的时间和。
jstat的主要工具选项:
-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编译的方法
1.3 jinfo:Java配置信息工具
jinfo(Configuration Info for Java)实时的查看和调整虚拟机的各项参数,使用jps命令的-v参数可以查看虚拟机启动时显式指定的参数列表,但如果想知道未被显式指定的参数的系统默认值,除了找资料外,就只能使用jinfo的-flag选项进行查询了。
[root@AY131228205040795d8cZ bin]# jinfo -flag CMSInitiatingOccupancyFraction 7862
-XX:CMSInitiatingOccupancyFraction=-1
1.4 jmap:Java内存映像工具
jmap(Memory Map for Java)命令用于生成堆转储快照(一般称为headdump或dump文件)。还可以查询finalize执行队列,Java堆和永久代的详细信息,如空间使用率、当前用的是哪种收集器等。
这里我用jmap生成了tomcat服务器的快照文件:
[root@AY131228205040795d8cZ home]# cd yuxi/
[root@AY131228205040795d8cZ my]# jmap -dump:format=b,file=apache.bin 7862
Dumping heap to /home/my/apache.bin ...
Heap dump file created
jmap的选项:
-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平台下有效
1.5 jhat:虚拟机堆转储快照分析工具
jhat(JVM Heap Analysis Tool)与jmap搭配使用,分析jmap生成的堆转储快照。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,可以在浏览器中查看。不过在实际中不使用jhat来分析dump,主要原因:1.一般不在部署应用的服务器上直接分析dump文件,而要复制到其他机器上进行分析,因为是耗时且消耗硬件资源。2.jhat功能比较简陋,如VisualVM,Eclipse Momery Analyzer、IBM HeapAnalyzer等都更强大。
下面是刚刚用jmap导出的apache.bin的jhat过程
[root@AY131228205040795d8cZ my]# jhat apache.bin
Reading from apache.bin...
Dump file created Wed Jun 11 19:40:33 CST 2014
Snapshot read, resolving...
Resolving 1422191 objects...
Chasing references, expect 284 dots............................................................................................................................................................................................................................................................................................
Eliminating duplicate references............................................................................................................................................................................................................................................................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
当屏幕显示“Server is ready”时,可以在浏览器中键入http://IP:7000来看到分析结果,如图:
分组显示以包为单位,可以点击链接看到每个类的信息。
1.6 jstack:Java堆栈跟踪工具
jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump或者javacore文件)。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做些什么事情,或者等待什么资源。
[root@AY131228205040795d8cZ yuxi]# jstack -l 7862
2014-06-12 11:04:05
Full thread dump Java HotSpot(TM) 64-Bit Server VM (20.45-b01 mixed mode):
"Attach Listener" daemon prio=10 tid=0x00007f6a84013800 nid=0xa8f waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"http-bio-8080-exec-345" daemon prio=10 tid=0x00007f6a980ee000 nid=0x5e31 waiting on condition [0x00007f6a715d4000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000e22bf280> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1987)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:399)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:957)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:917)
at java.lang.Thread.run(Thread.java:662)
Locked ownable synchronizers:
- None
JDK1.5中,java.lang.Thread类新增了一个getAllStackTrace()方法用于获取虚拟机中所有线程的StackTraceElement对象。使用这个方法可以通过简单的几行代码就完成jstack的大部分功能,在实际项目可以调用这个方法做个管理员页面,可以随时使用浏览器查看线程堆栈,如代码所示:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<html>
<head>
<title>服务器线程信息</title>
</head>
<body>
<pre>
<%
for(Map.Entry<Thread, StackTraceElement[]> stackTrace : Thread.getAllStackTraces().entrySet()){
Thread thread = (Thread)stackTrace.getKey();
StackTraceElement[] stack = (StackTraceElement[])stackTrace.getValue();
if(thread.equals(Thread.currentThread())){
continue;
}
out.print("\n线程:"+thread.getName()+"\n");
for(StackTraceElement element : stack){
out.print("\t"+element+"\n");
}
}
%>
</pre>
</body>
</html>
1.7 HSDIS:JIT生成代码反汇编
HSDIS是一个Sun官方推荐的HotSpot虚拟机JIT编译代码的反汇编插件,它包含在HotSpot虚拟机的源码之中,但没有提供编译后的程序。它的作用是让HotSpot的-XX:+PrintAssembly指令调用它来把动态生成的本地代码还原为汇编代码输出,同时还生成大量非常有价值的注释。
这里,我使用的是Linux操作系统,在https://kenai.com/projects/base-hsdis/downloads网页中选择自己的插件,放入jre/bin/server目录下,就是libjvm.so的同一目录下,之后通过 -XX:+PrintAssembly指令来使用插件,如果提示这个指令找不到,那么就要在所有参数之前加入一个 -XX:+UnlockDiagnosticVMOptions参数。
定义一个简单的java文件:
public class Bar {
int a = 1;
static int b = 2;
public int sum(int c){
return a+b+c;
}
public static void main(String[] args) {
new Bar().sum(3);
}
}
之后使用javac编译,编译后,执行时使用下边命令:
[root@AY131228205040795d8cZ my]# java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp -XX:CompileCommand=dontinline,*Bar.sum -XX:CompileCommand=compileonly,*Bar.sum Bar
这个命令中,参数-Xcomp是让虚拟机以编译模式执行代码,这样代码可以“偷懒”,不需要执行足够次数来预热就能出发JIT编译。两个-XX:CompileCommand的意思是让编译器不要内联sum()并且只编译sum(),-XX:+PrintAssembly就是输出反汇编内容。如果一切顺利的话,那么屏幕上会出现类似下边的代码:
我的程序输出的内容如下:
Java HotSpot(TM) 64-Bit Server VM warning: PrintAssembly is enabled; turning on DebugNonSafepoints to gain additional output
CompilerOracle: dontinline *Bar.sum
CompilerOracle: compileonly *Bar.sum
Loaded disassembler from hsdis-amd64.so
Decoding compiled method 0x00007f89c905fdd0:
Code:
[Disassembling for mach='i386:x86-64']
[Entry Point]
[Constants]
# {method} 'sum' '(I)I' in 'Bar'
# this: rsi:rsi = 'Bar'
# parm0: rdx = int
# [sp+0x20] (sp of caller)
0x00007f89c905ff20: mov 0x8(%rsi),%r10d
0x00007f89c905ff24: cmp %r10,%rax
0x00007f89c905ff27: jne 0x00007f89c9037620 ; {runtime_call}
0x00007f89c905ff2d: xchg %ax,%ax
[Verified Entry Point]
0x00007f89c905ff30: push %rbp
0x00007f89c905ff31: sub $0x10,%rsp
0x00007f89c905ff35: nop ;*synchronization entry
; - Bar::sum@-1 (line 7)
0x00007f89c905ff36: mov $0xfb0818c8,%r10 ; {oop('Bar')}
0x00007f89c905ff40: mov 0x260(%r10),%r10d
0x00007f89c905ff47: add 0xc(%rsi),%r10d
0x00007f89c905ff4b: mov %edx,%eax
0x00007f89c905ff4d: add %r10d,%eax ;*iadd
; - Bar::sum@9 (line 7)
0x00007f89c905ff50: add $0x10,%rsp
0x00007f89c905ff54: pop %rbp
0x00007f89c905ff55: test %eax,0xbe130a5(%rip) # 0x00007f89d4e73000
; {poll_return}
0x00007f89c905ff5b: retq
0x00007f89c905ff5c: hlt
0x00007f89c905ff5d: hlt
0x00007f89c905ff5e: hlt
0x00007f89c905ff5f: hlt
[Exception Handler]
[Stub Code]
0x00007f89c905ff60: jmpq 0x00007f89c905cfa0 ; {no_reloc}
[Deopt Handler Code]
0x00007f89c905ff65: callq 0x00007f89c905ff6a
0x00007f89c905ff6a: subq $0x5,(%rsp)
0x00007f89c905ff6f: jmpq 0x00007f89c90387c0 ; {runtime_call}
0x00007f89c905ff74: add %al,(%rax)
0x00007f89c905ff76: add %al,(%rax)
一些命令的意思:
1. push %rbp :保存上一栈帧基址
2. sub $0x10,%rsp :给新帧分配空间
3. mov $0xfb0818,%r10 :取方法区的指针
4. mov 0x260(%r10),%r10d :取类变量b,这里是访问方法区中的数据
5. add 0xc(%rsi),%r10d :这里将实例变量a和上一句取得的b相加,放入r10d中
6. mov %edx,%eax :在上边“parm0:rdx=int”,说明c在rdx中,这里rdx应与edx一致,将c放入eax中
7. add %r10d, %eax :将r10d中a与b的和加上eax中的c,结果放入eax中,计算a+b+c完成
8. add $0x10,%rsp :对应上文的rsp,这里是撤销栈帧
9. pop %rbp :对应上文的push,这里是恢复上一栈帧
10. test %eax, 2xbe130a5(%rip) :轮询方法返回处的Safepoint
11. retq 方法返回
2. JDK的可视化工具
2.1 Jconsole:Java监视与管理控制台
Jconsole(Java Monitoring and Management Console)是一种基于JMX的可视化监视、管理工具。
2.2 VisualVM:多合一故障处理工具
VisualVM(All-in-One Java Troubleshooting Tool)是到目前为止随JDK发布的功能最强大的运行监视和故障处理工具。