JVM(十六)与jvm相关的JDK命令行工具

来源:互联网 发布:matlab矩阵最大值位置 编辑:程序博客网 时间:2024/06/05 16:31

这里写图片描述

jps:虚拟机进程状况工具

jps命令格式:
jps[options][hostid]
这里写图片描述

jps执行样例:
这里写图片描述

jstat:虚拟机统计信息监视工具
可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据

jstat命令格式为:
jstat[option vmid[interval[s|ms][count]]]

如果是本地虚拟机进程,VMID与LVMID是一致的,如果是远程虚拟机进程,那VMID的格式应当是:
[protocol:][//]lvmid[@hostname[:port]/servername]

参数interval和count代表查询间隔和次数,如果省略这两个参数,说明只查询一次。
假设需要每250毫秒查询一次进程2764垃圾收集状况,一共查询20次,
那命令应当是:jstat-gc 2764 250 20
这里写图片描述

jstat执行样例:jstat-gcutil 2764
这里写图片描述

jinfo:Java配置信息工具

实时地查看和调整虚拟机各项参数

jinfo命令格式:jinfo[option]pid

执行样例:查询CMSInitiatingOccupancyFraction参数值。
jinfo-flag CMSInitiatingOccupancyFraction 1444
-XX:CMSInitiatingOccupancyFraction=85

jinfo(Configuration Info for Java)的作用是实时地查看和调整虚拟机各项参数。使用jps命令的-v参数可以查看虚拟机启动时显式指定的参数列表,但如果想知道未被显式指定的参数的系统默认值,除了去找资料外,就只能使用jinfo的-flag选项进行查询了(如果只限于JDK 1.6或以上版本的话,使用java-XX:+PrintFlagsFinal查看参数默认值也是一个很好的选择),jinfo还可以使用-sysprops选项把虚拟机进程的System.getProperties()的内容打印出来。这个命令在JDK 1.5时期已经随着Linux版的JDK发布,当时只提供了信息查询的功能,JDK 1.6之后,jinfo在Windows和Linux平台都有提供,并且加入了运行期修改参数的能力,可以使用-flag[+|-]name或者-flag name=value修改一部分运行期可写的虚拟机参数值。JDK 1.6中,jinfo对于Windows平台功能仍然有较大限制,只提供了最基本的-flag选项。

jmap:Java内存映像工具
用于生成堆转储快照(一般称为heapdump或dump文件)
jmap命令格式:
jmap[option]vmid
这里写图片描述

使用jmap生成一个正在运行的Eclipse的dump快照文件的例子,例子中的3500是通过jps命令查询到的LVMID。

jmap-dump:format=b,file=eclipse.bin 3500

Dumping heap to C:\Users\IcyFenix\eclipse.bin……
Heap dump file created

jhat:虚拟机堆转储快照分析工具
Sun JDK提供jhat(JVM Heap Analysis Tool)命令与jmap搭配使用,来分析jmap生成的堆转储快照

使用jhat分析4.2.4节中采用jmap生成的Eclipse IDE的内存快照文件。

代码清单4-3 使用jhat分析dump文件
C:\Users\IcyFenix>jhat eclipse.bin
Reading from eclipse.bin……
Dump file created Fri Nov 19 22:07:21 CST 2010
Snapshot read,resolving……
Resolving 1225951 objects……
Chasing references,expect 245 dots……
Eliminating duplicate references……
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
屏幕显示“Server is ready.”的提示后,用户在浏览器中键入http://localhost:7000/就可以看到分析结果,如图4-3所示:
这里写图片描述

分析结果默认是以包为单位进行分组显示,分析内存泄漏问题主要会使用到其中的“Heap Histogram”(与
jmap-histo功能一样)与OQL页签的功能,前者可以找到内存中总容量最大的对象,后者是标准的对象查询语言,
使用类似SQL的语法对内存中的对象进行查询统计

jstack:Java堆栈跟踪工具
jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump或者javacore文件)
线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做些什么事情,或者等待着什么资源。

jstack命令格式:
jstack[option]vmid
这里写图片描述

HSDIS:JIT生成代码反汇编

在Java虚拟机规范中,详细描述了虚拟机指令集中每条指令的执行过程、执行前后对操作数栈、局部变量表的影响等细节。这些细节描述与Sun的早期虚拟机(Sun Classic VM)高度吻合,但随着技术的发展,高性能虚拟机真正的细节实现方式已经渐渐与虚拟机规范所描述的内容产生了越来越大的差距,虚拟机规范中的描述逐渐成了虚拟机实现的“概念模型”——即实现只能保证规范描述等效。基于这个原因,我们分析程序的执行语义问题(虚拟机做了什么)时,在字节码层面上分析完全可行,但分析程序的执行行为问题(虚拟机是怎样做的、性能如何)时,在字节码层面上分析就没有什么意义了,需要通过其他方式解决。

分析程序如何执行,通过软件调试工具(GDB、Windbg等)来断点调试是最常见的手段,但是这样的调试方式在Java虚拟机中会遇到很大困难,因为大量执行代码是通过JIT编译器动态生成到CodeBuffer中的,没有很简单的手段来处理这种混合模式的调试(不过相信虚拟机开发团队内部肯定是有内部工具的)。因此,不得不通过一些特别的手段来解决问题,基于这种背景,本节的主角——HSDIS插件就正式登场了。

HSDIS是一个Sun官方推荐的HotSpot虚拟机JIT编译代码的反汇编插件,它包含在HotSpot虚拟机的源码之中,但没有提供编译后的程序。在Project Kenai的网站[1]也可以下载到单独的源码。它的作用是让HotSpot的-XX:+PrintAssembly指令调用它来把动态生成的本地代码还原为汇编代码输出,同时还生成了大量非常有价值的注释,这样我们就可以通过输出的代码来分析问题。读者可以根据自己的操作系统和CPU类型从Project Kenai的网站上下载编译好的插件,直接放到JDK_HOME/jre/bin/client和JDK_HOME/jre/bin/server目录中即可。如果没有找到所需操作系统(譬如Windows的就没有)的成品,那就得自己使用源码编译一下[2]。

还需要注意的是,如果读者使用的是Debug或者FastDebug版的HotSpot,那可以直接通过-XX:+PrintAssembly指令使用插件;如果使用的是Product版的HotSpot,那还要额外加入一-XX:+UnlockDiagnosticVMOptions参数。笔者以代码清单4-6中的简单测试代码为例演示一下这个插件的使用。

代码清单4-6 测试代码

public class Bar{int a=1static int b=2public 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编译[3]。两个-XX:CompileCommand意思是让编译器不要内联sum()并且只编译sum(),-XX:+PrintAssembly就是输出反汇编内容。如果一切顺利的话,那么屏幕上会出现类似下面代码清单4-7所示的内容。

代码清单4-7 测试代码

[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),%eax0x01cac40a:jne 0x01c6b050;{runtime_call}[Verified Entry Point]0x01cac410:mov%eax,-0x8000%esp0x01cac417:push%ebp0x01cac418:sub$0x18,%esp*aload_0;-test.Bar:sum@0(line 8);block B0[010]0x01cac41b:mov 0x8%ecx),%eax*getfield a;-test.Bar:sum@1(line 80x01cac41e:mov$0x3d2fad8,%esi;{oop(a'java/lang/Class'='test/Bar')}0x01cac423:mov 0x68%esi),%esi*getstatic b;-test.Bar:sum@4(line 80x01cac426:add%esi%eax0x01cac428:add%edx%eax0x01cac42a:add$0x18,%esp0x01cac42d:pop%ebp0x01cac42e:test%eax0x2b0100;{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:做两次加法,求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:方法返回

原创粉丝点击