6、虚拟机性能监控与故障处理工具(1)(JVM笔记)
一、JDK的命令行工具
所有的命令行工具都在bin
目录中。Sun JDK
监控和故障处理工具如下:
名称 | 主要作用 |
---|---|
jps |
JVM Process Status Tool ,显示指定系统内所有的HotSpot 虚拟机进程 |
jstat |
JVM Statistics Monitoring Tool ,用于收集HotSpot 虚拟机各方面的运行数据 |
jinfo |
Configuration Info for Java ,显示虚拟机配置信息 |
jmap |
Memory Map for Java ,生成虚拟机的内存转储快照(headdump 文件) |
jhat |
JVM Heap Dump Browser ,用于分析headdump 文件,它会建立一个HTTP/HTML 服务器,让用户可以在浏览器上查看分析结果 |
jstack |
Stack Trace for Java ,显示虚拟机的线程快照 |
1.1 jps:虚拟机进程状况工具
此工具可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class, main()
函数所在的类)名称以及这些进程的本地虚拟机唯一ID(Local Virtual Machine Identifier,LVMID)
。其他的JDK
工具大多需要输入它查询到的LVMID
来确定要监控的是哪一个虚拟机进程。对于本地虚拟机进程来说,LVMID
与操作系统的进程ID(Process Identifier,PID)
是一致的,使用windows
的任务管理器或UNIX
的ps
命令也可以查询到虚拟机进程的LVMID
,但如果同时启动了多个虚拟机进程,无法根据进程名称定位时,那就只能依赖此命令显示主类的功能才能区分了。命令格式如下:
jps [ options ] [ hostid ]
其主要选项如下:
选项 | 作用 |
---|---|
-q |
只输出LVMID ,省略主类的名称 |
-m |
输出虚拟机进程启动时传递给主类main() 函数的参数 |
-l |
输出主类的全名,如果进程执行的是Jar 包,输出Jar 路径 |
-v |
输出虚拟机进程启动时JVM 参数 |
1.2 jstat:虚拟机统计信息监视工具
此工具用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT
编译等运行数据。其命令格式为:
jstat [ option vmid [ interval [ s|ms ] [ count ] ] ]
说明:如果是本地虚拟机进程,VMID
与LVMID
是一致的,如果是远程虚拟机进程,那VMID
的格式应当是:
[ protocol: ][ // ] lvmid [ @hostname [ :port ] / servername ]
其中interval
和count
代表查询间隔和次数,如果省略这两个参数,说明只查询一次。假设需要250ms
查询一次进程2764
垃圾收集状况,一共查询20
次,那命令应该是:
jstat -gc 2764 250 20
选项option
代表着用户希望查询的虚拟机信息,主要分为三类:类装载、垃圾收集、运行期编译状况,具体选项及作用如下:
选项 | 作用 |
---|---|
-class |
监视类装载、卸载数量、总空间以及类装载所耗费的时间 |
-gc |
监视Java 堆状况,包括Eden 区、两个survivor 区、老年代、永久代等的容量、已用空间、GC 时间合计等信息 |
-gccapacity |
监视内容与-gc 基本相同,但输出主要关注Java 堆各个区域使用到的最大、最小空间 |
-gcutil |
监视内容与-gc 基本相同,但输出主要关注已使用空间占总空间的百分比 |
-gccause |
与-gcutil 功能一样,但是会额外输出导致上一次GC 产生的原因 |
-gcnew |
监视新生代GC 状况 |
-gcnewcapacity |
监视内容与-gcnew 基本相同,输出主要关注使用到的最大、最小空间 |
-gcold |
监视老年代GC 状况 |
-gcoldcapactiy |
监视内容与-gcold 基本相同,输出主要关注使用到的最大、最小空间 |
-gcpermcapacity |
输出永久代使用到的最大、最小空间 |
-compiler |
输出JIT 编译器编译过的方法、耗时等信息 |
-printcompilation |
输出已经被JIT 编译的方法 |
下面通过一个例子简单了解一下:
说明:这台服务器的新生代
Eden
区(E
,表示Eden
)使用了6.2%
的空间,两个survivor
区(S0、S1
,表示survivor0、survivor1
)里面都是空的,老年代(O
,表示Old
)和永久代(P
,表示Permanent
)则分别使用了41.42%
和47.20%
的空间。程序运行以来共发生Minor GC
(YGC
,表示Young GC
)16
次,总耗时0.105s
,发生Full GC
(FGC
,表示Full GC
)三次,Full GC
总耗时(FGCT
,表示Full GC Time
)为0.472s
,所有GC
总耗时(GCT
,表示GC Time
)为0.577s
。
1.3 jinfo:Java配置信息工具
此工具是实时地查看和调整虚拟机各项参数。使用jps
命令的-v
参数可以查看虚拟机启动时显式指定的参数列表,但如果想知道未被显式指定的参数的系统默认值,除了去找资料外,就只能使用jinfo
的-flag
选项进行查询了(如果只限于JDK1.6
或以上版本,则使用java -XX:+PrintFlagsFinal
查看参数默认值也是一个很好的选择),jinfo
还可以使用-sysprops
选项把虚拟机进程的Sytem.getProperties()
的内容打印出来,还有在运行期修改参数的能力,可以使用-flag[+|-] name
或-flag name=value
修改一部分运行期可写的虚拟机参数值。在windows
中有较大限制,只提供了最基本的-flag
选项。其命令格式如下:
jinfo [ option ] pid
1.4 jmap:Java内存映像工具
此命令用于生成堆转储快照(一般称为headdump
或dump
文件)。如果不实用此命令,要想获取Java
堆转储快照,可以使用之前提到的暴力手段,即使用-XX:+HeadDumpOnOutOfMemoryError
参数,可以让虚拟机在OOM
异常出现之后自动生成dump
文件,通过-XX:+HeadDumpOnCtrlBreak
参数则可以使用[Ctrl]+[Break]
键让虚拟机生成dump
文件,又或者在Linux
中使用Kill -3
命令拿到dump
文件。当然,此命令的作用不仅仅是为了获取dump
文件,它还可以查询finalize
执行队列、Java
堆和永久代的详细信息,如空间使用率、当前用的是哪种收集器等。其功能在windows
中受限,除了生成dump
文件的-dump
选项和用于查看每个类的实例、空间占用统计的-histo
选项在所有操作系统都提供之外,其余选项都智能在Linux/Solaris
下使用。其命令格式:
jmap [ option ] vmid
主要选项如下:
选项 | 作用 |
---|---|
-dump |
生成Java 堆转储快照。格式为:-dump:[live, ]format=b, file=<filename> , 其中live 子参数说明是否只dump 出存活的对象 |
-finalizerinfo |
显示在F-Queue 中等待Finalizer 线程执行finalizer 方法的对象。只在Linux/Solaris 平台有效 |
-heap |
显示Java 堆详细信息,如使用哪种回收期、参数配置、分代状况等。只在Linux/Solaris 平台有效 |
-histo |
显示堆中对象统计信息,包括类、实例数量、合计容量 |
-permstat |
以ClassLoader 为统计口径显示永久代内存状态。只在Linux/Solaris 平台有效 |
-F |
当虚拟机进程对-dump 选项没有响应时,可使用这个选项强制生成dump 快照。只在Linux/Solaris 平台有效 |
例如:(3500
是LVMID
)
1.5 jhat:虚拟机堆转储快照分析工具
此命令和jmap
搭配使用,来分析jmap
生成的堆转储快照。其内置一个微型的HTTP/HTML
服务器,生成dump
文件的分析结果后,可以在浏览器中查看。一般不会使用此命令来分析dump
文件,因为一是一般不会在部署应用的服务器上直接分析dump
文件(太耗资源),而是此工具功能相对较为简陋,可以使用后面讲到的VisualVM
工具替代。
1.6 jstack:Java堆栈跟踪工具
此命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump
或者javacore
文件)。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。线程出现停顿的时候通过此命令来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么,或等待什么资源。其格式为:
jstack [ option ] vmid
主要选项如下:
选项 | 作用 |
---|---|
-F |
当正常输出的请求不被响应时,强制输出线程堆栈 |
-l |
除堆栈外,显示关于锁的附加信息 |
-m |
如果调用到本地方法的话,可以显示C/C++ 的堆栈 |
在JDK1.5中,java.lang.Thread类新增了一个getAllStackTraces()方法用于获取虚拟机中所有线程的StackTraceElement对象。使用此方法通过几行简单的代码就可以完成jstack的大部分功能,在实际项目中不妨调用此方法做个管理员页面,随时查看线程堆栈。
3
1.7 HSDIS:JIT生成代码反汇编
随着技术的发展,高性能虚拟机真正的细节实现方式逐渐成了虚拟机实现的“概念模型”——即实现智能保证规范描述等效。基于此原因,在分析程序的执行语义问题(虚拟机做了什么)时,在字节码层面上分析完全可行,但分析程序的执行行为问题(虚拟机是怎样做的、性能如何)时,在字节码层面上分析居没有什么意义了,需要通过其他方式解决。
HSDIS
是官方对剑的HotSpot
虚拟机JIT
编译代码的反汇编插件,它包含在HotSpot
虚拟机的源码之中,但没有提供编译后的程序。其作用是让HotSpot
的-XX:+PrintAssembly
指令调用它来把动态生成的本地代码还原为汇编输出,同时还生成大量非常有价值的注释,这样我们就可以通过输出的代码来分析问题。注意,如果使用的Debug
或FastDebug
版的HotSpot
,那可以直接通过-XX:+PrintAssembly
指令使用插件;如果使用的是Product
版的HotSpot
,那还要额外加入一个-XX:+UnlockDiagnosticVMOptions
参数。
例如:测试代码
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);
}
}
编译:
java -XX+:PrintAssembly -Xcomp -XX:CompileCommand=dontinline, *Bar.sum -XX:CompileCommand=Compileonly, *Bar.sum test.Bar
其中-Xcomp
是让虚拟机以编译模式执行代码,这样就不需要执行足够次数来预热就能触发JIT
编译(-Xcomp
在新版本HotSpot
已被移除)。两个-XX:CompileCommand
是让编译器不要内联sum()
并且只编译sum()
,-XX+:PrintAssembly
就是输出反汇编内容。如果一切顺利会得到如下代码:
[Disassembling for mach='i386']
[Entry Point]
[Constants]
# {method} 'sum' '(I)I' in 'test/Bar'
# this: ecx = 'test/Bar'
# parm0: edx = int
# [sp+0x20] (sp of caller)
……
0x01cac407: cmp 0x4(%ecx),%eax
0x01cac40a: jne 0x01c6b050 ; {runtime_call}
[Verified Entry Point]
0x01cac410: mov %eax,-0x8000(%esp)
0x01cac417: push %ebp
0x01cac418: sub $0x18,%esp ;*aload_0
; - test.Bar::sum@0 (line 8)
;; block B0 [0, 10]
0x01cac41b: mov 0x8(%ecx),%eax ;*getfield a
; - test.Bar::sum@1 (line 8)
0x01cac41e: mov $0x3d2fad8,%esi ; {oop(a
'java/lang/Class' = 'test/Bar')}
0x01cac423: mov 0x68(%esi),%esi ;*getstatic b
; - test.Bar::sum@4 (line 8)
0x01cac426: add %esi,%eax
0x01cac428: add %edx,%eax
0x01cac42a: add $0x18,%esp
0x01cac42d: pop %ebp
0x01cac42e: test %eax,0x2b0100 ; {poll_return}
0x01cac434: ret
说明:
- 1、
mov %eax,-0x8000(%esp)
:检查栈溢。 - 2、
push %ebp
:保存上一栈帧基址。 - 3、
sub $0x18,%esp
:给新帧分配空间。 - 4、
mov 0x8(%ecx),%eax
:取实例变量a
,这里0x8(%ecx)
就是ecx+0x8
的意思,前面“[Constants]”
节中提示了“this:ecx = 'test/Bar'”
,即ecx
寄存器中放的就是this
对象的地址。偏移0x8
是越过this
对象的对象头,之后就是实例变量a
的内存位置。这次是访问“Java堆”
中的数据。 - 5、
mov $0x3d2fad8,%esi
:取test.Bar
在方法区的指针。 - 6、
mov 0x68(%esi),%esi
:取类变量b
,这次是访问“方法区”中的数据。 - 7、
add %esi,%eax 、add %edx,%eax
:做2
次加法,求a+b+c
的值,前面的代码把a
放在eax
中,把b
放在esi
中,而c
在[Constants]
中提示了,“parm0:edx = int”
,说明c
在edx
中。 - 8、
add $0x18,%esp
:撤销栈帧。 - 9、
pop %ebp
:恢复上一栈帧。 - 10、
test %eax,0x2b0100
:轮询方法返回处的SafePoint
- 11、
ret
:方法返回。