在JDK的开发包中,除了大家熟知的JAVA.exe和javac.exe外,还有一系列辅助工具。这些辅助工具位于JDK安装目录下的bin目录中,可以帮助开发人员很好地解决Java应用程序的一些“疑难杂症”。图6.16显示了部分辅助工具。
乍看之下,虽然这些工具都是.exe的可执行文件,但事实上它们只是Java程序的一层包装,其真正的实现是在tools.jar中,如图6.17所示。
以jps工具为例,在控制台执行jps命令和
java-classpath%Java_HOME%/lib/tools.jarsun.tools.jps.Jps命令是等价的,即jps.exe只是这个命令的一层包装。
jps命令
jps命令类似于linux下的ps,但它只用于列出Java的进程。直接运行jps命令不加任何参数,可以列出Java程序进程ID及Main函数短名称,例如:
从这个输出结果中可以看到,当前系统中共存在3个Java应用程序,其中第一个输出Jps就是jps命令本身,这也证明了此命令的本质是一个Java程序。此外,jps还提供了一系列参数来控制它的输出内容。
参数-q可以指定jps只输出进程ID,而不输出类的短名称,例如:
参数-m可以用于输出传递给Java进程(主函数)的参数,例如:
参数-l可以用于输出主函数的完整路径,例如:
参数-v可以显示传递给JVM的参数,例如:
注意:jps命令类似于ps命令,但是它只列出系统中所有的Java应用程序。通过jps命令可以方便地查看Java进程的启动类、传入参数和JVM参数等信息。
jstat命令
jstat是一个可用于观察Java应用程序运行信息的工具。它的功能非常强大,可以通过它查看堆信息的详细情况。其基本使用语法如下:
其中,选项option可以由以下值构成。
·-class:显示ClassLoader的相关信息。
·-compiler:显示JIT编译的相关信息。
·-gc:显示与GC操作相关的堆信息。
·-gccapacity:显示各个代的容量及使用情况。
·-gccause:显示垃圾收集的相关信息(同-gcutil),同时显示最后一次或当前正在发生的垃圾收集的诱发原因。
·-gcnew:显示新生代信息。
·-gcnewcapacity:显示新生代的大小与使用情况。
·-gcold:显示老年代和永久代的信息。
·-gcoldcapacity:显示老年代的大小。
·-gcpermcapacity:显示永久代的大小。
·-gcutil:显示垃圾收集信息。
·-printcompilation:输出JIT编译的方法信息。相关参数含义如下:
·-t:可以在输出信息前加上一个Timestamp列,显示程序的运行时间。
·-h:可以设定在周期性数据输出时,当输出多少行数据后跟着输出一个表头信息。
·interval:用于指定输出统计数据的周期,单位为ms。
·count:用于指定一共输出多少次数据。
如下示例输出Java进程2972的ClassLoader相关信息。每秒统计一次信息,一共输出2次。
在以上的输出结果中,Loaded表示载入类的数量,第1个Bytes表示载入类的合计大小,Unloaded表示卸载类的数量,第2个Bytes表示卸载类的大小,Time表示在加载类和卸载类上所花的时间。
下例显示了查看JIT编译的信息。
其中,Compiled表示编译任务执行的次数,Failed表示编译失败的次数,Invalid表示编译不可用的次数,Time表示编译的总耗时,FailedType表示最后一次编译失败的类型,FailedMethod表示最后一次编译失败的类名和方法名。
下例显示了与GC操作相关的堆信息输出结果。
其中,各列的信息含义如下:
·S0C:s0(from)的大小(KB)。
·S1C:s1(from)的大小(KB)。
·S0U:s0(from)已使用的空间(KB)。
·S1U:s1(from)已使用的空间(KB)。
·EC:eden区的大小(KB)。
·EU:eden区已使用的空间(KB)。
·OC:老年代的大小(KB)。
·OU:老年代已经使用的空间(KB)。
·PC:永久区的大小(KB)。
·PU:永久区已使用的空间(KB)。
·YGC:新生代GC操作次数。
·YGCT:新生代GC操作耗时。
·FGC:FullGC操作次数。
·FGCT:FullGC操作耗时。
·GCT:GC操作总耗时。
下例显示了各个代的信息,与-gc相比,它不仅输出了各个代的当前大小,也包含各个代的最大值和最小值。
其中部分列的信息含义如下:
·NGCMN:新生代最小值(KB)。
·NGCMX:新生代最大值(KB)。
·NGC:当前新生代大小(KB)。
·OGCMN:老年代最小值(KB)。
·OGCMX:老年代最大值(KB)。
·PGCMN:永久代最小值(KB)。
·PGCMX:永久代最大值(KB)。
下例显示了最近一次执行GC操作的原因及当前执行GC操作的原因。
其中部分列的信息含义如下:
·LGCC:上次执行GC操作的原因。
·GCC:当前执行GC操作的原因。
以上输出结果显示,最近一次执行GC操作是由于显式的System.gc()调用所引起的,当前时刻未执行GC操作。
-gcnew参数可以用于查看新生代的一些详细信息。
其中部分列的信息含义如下:
·TT:新生代对象晋升到老年代对象的年龄。
·MTT:新生代对象晋升到老年代对象的年龄最大值。
·DSS:所需的survivor区大小。
-gcnewcapacity参数可以详细地输出新生代各个区的大小信息。
其中部分列信息如下:
·S0CMX:s0区的最大值(KB)。
·S1CMX:s1区的最大值(KB)。
·ECMX:eden区的最大值(KB)。
-gcold参数用于展现老年代GC操作的概况。
-gcoldcapacity参数用于展现老年代的容量信息。
-gcpermcapacity参数用于展示永久区的使用情况。
-gcutil参数用于展示GC回收的相关信息。
其中部分列的信息如下:
·S0:s0区使用的百分比。
·S1:s1区使用的百分比。
·E:eden区使用的百分比。
·O:old区使用的百分比。
·P:永久区使用的百分比。
注意:jstat命令可以非常详细地查看Java应用程序的堆使用情况及GC操作情况。
jinfo命令
jinfo可以用来查看正在运行的Java应用程序的扩展参数,甚至支持在运行时修改部分参数。其基本语法如下:
其中,option可以为以下信息:
·-flag<name>:打印指定JVM的参数值。
·-flag[+|-]<name>:设置指定JVM参数的布尔值。
·-flag<name>=<value>:设置指定JVM参数的值。
在很多情况下,Java应用程序不会指定所有的JVM参数,此时,开发人员可能不知道某一个具体的JVM参数的默认值。在这种情况下,需要通过查找文档获取某个参数的默认值。这个查找过程可能是非常艰难的,但有了jinfo工具,开发人员可以很方便地找到JVM参数的当前值。
例如,下例显示了新生代对象晋升到老年代对象的最大年龄。在应用程序启动时并没有指定这个参数,但通过jinfo可以查看这个参数的当前数值。
下例显示了是否打印GC的详细信息。
除了查找参数的值,jinfo也支持修改部分参数的数值,当然这个修改能力是极其有限的。下例中通过调用jinfo对PrintGCDetails参数进行修改,jinfo可以在Java程序运行时关闭或者打开这个开关。
注意:jinfo不仅可以查看运行时某一个JVM参数的实际取值,甚至可以在运行时修改部分参数,并使之立即生效。
jmap命令
jmap命令可以生成Java应用程序的堆快照和对象的统计信息。
下例使用jmap命令生成PID为2972的Java程序的对象统计信息,并输出到s.txt文件中。
可以看到,这个输出结果显示了内存中的实例数量和合计。
jmap命令的另一个更为重要的功能是得到Java程序的当前堆快照,例如执行以下命令:
本例中,将应用程序的堆快照输出到C盘的heap.bin文件中。之后,便可以通过多种工具分析该文件,例如6.3.5节中将要介绍的jhat工具。这里使用VisualVM工具打开这个快照文件,如图6.18所示。
注意:jmap命令可用于导出Java应用程序的堆快照。
jhat命令
使用jhat命令可以分析Java应用程序的堆快照内容。这里分析6.3.4节中使用jmap命令生成的堆文件heap.hprof,如下:
jhat在分析完成后,使用HTTP服务器展示其分析结果。在浏览器中访问http://127.0.0.1:7000,结果如图6.19所示。
在默认页中,jhat服务器显示了所有的非平台类信息。单击链接进入,可以查看选中类的超类、ClassLoader及该类的实例等信息。此外,在页面的底部,jhat还为开发人员提供了其他查询方式,如图6.20所示。
通过这些链接,开发者可以进一步查看所有类的信息(包括Java平台的类)、所有类的实例数量及实例的具体信息。最后,还有一个链接指向OQL查询界面。
图6.21显示了在jhat中查看Java应用程序中java.lang.String类的实例数量。
单击instances链接可以进一步查看String对象的实例,如图6.22所示。
通常,导出的堆快照信息非常多,因此可能很难通过页面上简单的链接索引找到想要的信息。为此,jhat还支持使用OQL语句对堆快照进行查询。执行OQL语句的界面非常简洁,如图6.23所示。例如,使用OQL查询出当前Java程序中所有java.io.File对象的路径,则OQL语句如下:
jhat的OQL语句与VisualVM的OQL非常接近,有兴趣的读者可以查阅本书中的相关章节。
注意:jhat命令可以对堆快照文件进行分析,它启动一个HTTP服务器,开发人员可以通过浏览器浏览Java堆快照。
jstack命令
jstack命令可用于导出Java应用程序的线程堆栈。其语法如下:
其中,-l选项用于打印锁的附加信息。
jstack工具会在控制台输出程序中所有的锁信息,并可以使用重定向将输出结果保存到文件中。例如:
下例演示了一个简单的死锁,两个线程分别占用south锁和north锁,并同时请求对方占用的锁,导致死锁发生。
使用jstack工具打印上例的输出结果,部分结果如下:
从jstack工具的输出结果中可以很容易地找到发生死锁的两个线程及死锁线程的持有对象和等待对象,从而帮助开发人员解决死锁问题。
注意:通过jstack工具不仅可以得到线程堆栈,还可以自动进行死锁检查,并输出找到的死锁信息。
jstatd命令
在本节之前所述的工具中,只涉及监控本机的Java应用程序,而在这些工具中,一些监控工具也支持对远程计算机的监控(如jps、jstat)。为了启用远程监控,则需要配合使用jstatd命令。
jstatd命令是一个RMI服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd服务器将本地的Java应用程序信息传递到远程计算机上,如图6.24所示。
直接打开jstatd服务器可能会抛出拒绝访问的异常:
这是由于jstatd程序没有足够的权限所致。可以使用Java的安全策略,为其分配相应的权限。下面的代码为jstatd分配了最大的权限,并将其保存在jstatd.all.policy文件中。
使用以下命令再次开启jstatd服务器:
这样,服务器开启成功。
注意:-J参数是一个公共参数,如jps和jstat等命令都可以接受这个参数。由于jps和jstat命令本身也是Java应用程序,-J参数可以为jps等命令本身设置其JVM参数。
默认情况下,jstatd将在1099端口开启RMI服务器。
以上命令行显示,本机的1099端口处于监听状态,相关进程号是3656。使用jsp命令查看3656进程正是jstatd,说明jstatd启动成功。
下面使用jps显示远程计算机的Java进程,执行命令如下:
使用jstat显示远程进程460的GC操作情况,执行命令如下:
hprof工具
与前文中介绍的监控工具不同,hprof不是独立的监控工具,它只是一个Javaagent工具,可以用于监控Java应用程序在运行时的CPU信息和堆信息。使用java-agentlib:hprof=help命令可以查看hprof的帮助文档。下面是hprof工具帮助信息的输出结果,加粗部分是常用的参数。
使用hprof工具可以查看程序中各个函数的CPU占用时间。以下代码包含3个方法,分别占用不同的CPU时间。
使用参数-agentlib:hprof=cpu=times,interval=10运行以上代码。times选项将会在Java函数的调用前后记录函数的执行时间,进而计算函数的执行时间。程序运行的部分输出结果如下:
可以很容易地看到运行时间最长的函数。
使用参数-agentlib:hprof=heap=dump,format=b,file=c:core.hprof运行程序,可以将应用程序的堆快照保存在指定文件c:core.hprof中。使用MAT或者VisualVM等工具可以分析这个堆文件。
使用参数-agentlib:hprof=heap=sites运行程序,可以输出Java应用程序中各个类所占的内存百分比。部分输出结果如下:
注意:使用hprof工具可以监控Java应用程序各个函数的运行时间,或导出程序的堆快照。
jcmd命令
在JDK7之后,JDK新增了一个命令行工具jcmd。不同于之前的命令行工具,jcmd是一个多功能工具,它几乎包括之前介绍的所有命令的功能。
如下命令类似于jps,列出当前正在运行的Java程序。
下面的命令显示了给定程序的启动时间。
不同于之前的命令行工具,jcmd的使用更加友好,它不仅支持pid作为输入,也可以使用主程序类名作为其输入,例如:
它足够“聪明”,甚至可以只写类的短名称,例如:
还可以通过jcmd很轻松地打印线程堆栈(类似于jstack),例如:
也可以通过jcmd打印类的柱状图信息(类似于jmap-histo),例如:
同样支持dump整个堆数据,例如:
上述命令会将整个堆信息转存到D:d.dump文件中。
还可以查看进程启动时的虚拟机参数,例如: