JVM 命令行工具的使用
作者:布道师小羊
性能问题是软件工程师在日常工作中需要经常面对和解决的问题,在用户体验至上的今天,解决好应用的性能问题能带来非常大的收益。工欲善其事,必先利其器,想要解决性能相关问题,必须要有比较好的性能诊断工具。Java作为最流行的编程语言之一,应用的性能诊断一直受到业界广泛关注。造成Java应用出现性能问题的因素非常多,例如线程控制、磁盘读写、数据库访问、网络I/O、垃圾收集等。想要定位这些问题,一款优秀的性能诊断工具必不可少,就好比中医、西医看病,中医讲究的是望、闻、问、切,西医则是借助各种检查仪器。
1、概述
JDK本身已经集成了很多诊断工具。在大家刚接触Java学习的时候,最先了解的两个命令就是javac和java,但是除此之外,还有一些其他工具可以使用,可是并非所有的程序员都了解其他命令行程序的作用,接下来我们一起看看其他命令行程序的作用。进入到安装JDK的bin目录,会发现还有一系列辅助工具。这些辅助工具用来获取目标JVM不同方面、不同层次的信息,帮助开发人员很好地解决Java应用程序的一些疑难杂症。Mac系统bin目录的内容如下图所示:
Windows系统bin目录的内容如下图所示:
虽然在Windows系统下都是exe格式的可执行文件。但事实上,它们只是Java程序的一层包装,其真正实现是在tools.jar中,如下图所示。以jps工具为例,在控制台执行jps命令和执行java -classpath %Java_HOME%/lib/tools.jar sun.tools.jps.Jps命令是等价的,即jps.exe只是这个命令的一层包装。下面介绍一些常用的命令工具。
2、jps:查看正在运行的Java进程
jps(JVM Process Status Tool)命令用于查看系统内所有的JVM进程,可根据参数选项指定是否显示JVM的执行主类[包含main()方法的类],以及进程的本地JVMID(Local Virtual Machine Identifier),对于本地JVM进程来说,进程的本地JVMID与操作系统的进程ID是一致的。简单来说,就是Java提供的一个显示当前所有Java进程pid的命令,和Linux系统里的ps命令很相似,ps命令主要是用来显示当前系统的进程情况,比如查看进程列表和进程ID。在日常工作中,此命令也是最常用的命令之一。
jps的基本使用语法如下:
jps [ options ] [ hostid ]
1、jps命令[options]选项说明:
2、[hostid]说明:
hostid表示目标主机的主机名或IP地址,如果省略该参数,则目标主机为本地主机。如果想要远程监控主机上的Java程序,需要安装jstatd。对于网络安全要求非常严格的场所,需要自定义策略文件来满足对特定的主机或网络的访问,但是这种技术容易受到IP地址欺诈攻击。如果由于安全问题无法通过定制的策略文件处理,那么最安全的操作是在主机本地使用jstat和jps工具。
3、使用案例:
Linux上启动Tomcat(一种Web应用服务器),然后在Linux上面使用ps命令查看Tomcat进程ID使用,如下所示:
ps -ef | grep"tomcat"
运行结果如下图所示:
可以看到进程ID是1224。使用jps -l命令查看,如下图上所示:
3、jstat:查看JVM统计信息
jstat(JVM Statistics Monitoring Tool)用于收集JVM各方面的运行数据,显示本地或远程JVM进程中的类装载、内存、垃圾收集、JIT编译等运行数据。在没有图形用户界面时,只提供了纯文本控制台环境的服务器上,它是运行期定位JVM性能问题的首选工具。常用于检测垃圾回收问题以及内存泄漏问题。它的功能非常强大,可以通过它查看堆信息的详细情况。
jstat的基本使用语法如下:
jstat -<option>[-t] [-h<lines>] <vmid>[<interval>[<count>]]
使用下面的命令可以查看jstat相关参数:
jstat -h 或 jstat -help
1、[options]选项说明
2、[-t]参数说明
[-t]参数可以在输出信息前加上一个Timestamp列,显示程序的运行时间。可以比较Java进程的启动时间以及总GC时间(GCT列),或者两次测量的间隔时间以及总GC时间的增量,来得出GC时间占运行时间的比例。如果该比例超过20%,则说明目前堆的压力较大;如果该比例超过90%,则说明堆里几乎没有可用空间,随时都可能抛出OOM异常。
3、[-h]参数说明
[-h]参数可以在周期性数据输岀时,输出设定的行数的数据后输出一个表头信息。
4、[interval]参数说明
[interval]参数用于指定输出统计数据的周期,单位为毫秒,简单来说就是查询间隔时间。
5、[count]参数说明
[count]用于指定查询的总次数。
6、使用案列
由于jstat参数选项比较多,这里只列举一个启动了Tomcat的Linux服务器案例,查看其监视状态,命令如下所示。
运行结果如下图所示:
运行结果的各列表示的含义如下表所示:
jstat还可以用来判断是否出现内存泄漏,步骤如下:
- (1)在长时间运行的Java程序中,可以运行jstat命令连续获取多行性能数据,并取这几行数据中OU列(即已占用的老年代内存)的最小值。
- (2)每隔一段较长的时间重复一次上述操作,获得多组OU最小值。如果这些值呈上涨趋势,则说明该Java程序的老年代内存已使用量在不断上涨,这意味着无法回收的对象在不断增加,因此很有可能存在内存泄漏。
4、jinfo:实时查看和修改JVM配置参数
jinfo(Configuration Info for Java)可用于查看和调整JVM的配置参数。在很多情况下,Java应用程序不会指定所有的JVM参数。而此时,开发人员可能不知道某一个具体的JVM参数的默认值。在这种情况下,可能需要通过查找文档获取某个参数的默认值。这个查找过程可能是非常艰难的。但有了jinfo工具,开发人员可以很方便地找到JVM参数的当前值。上面讲解的jps -v命令虽然可以查看JVM启动时显示指定的参数列表,但是如果想要知道未被显示指定的参数的系统默认值,就需要用到jinfo工具了。
第二个作用就是在程序运行时修改部分参数,并使之立即生效。并非所有参数都支持动态修改,只有被标记为manageable的参数可以被实时修改。其实,这个修改能力是极其有限的,使用下面的命令查看被标记为manageable的参数。
java -XX:+PrintFlagsFinal -version | grep manageable
运行结果如下图所示:
jinfo的基本使用语法如下:
jinfo [ options ] pid
1、[ options ]选项说明
jinfo工具options主要选项如下表所示:
2、pid说明
Java进程ID,必须要加上。
3、使用案例
由于jinfo可以查看JVM配置信息,也可用于调整JVM的配置参数,下面分两类案例分别讲解如何使用。
4、jinfo用于查看JVM配置信息案例
程序很简单,只需要保证程序在执行状态即可。首先使用jps命令查看进程ID,如下所示:
#查看进程ID jps -l
执行结果如下:
C:\Users\Administrator>jps -l
15008 com.yang.ScannerTest
14820 sun.tools.jps.Jps
上面出现两个结果,ScannerTest程序的进程ID是15008,另外一个进程ID表示jps命令本身的进程。下面使用jinfo命令来查看JVM配置参数,命令如下:
(1)根据进程ID查询全部参数和系统属性:
#查看全部参数和系统属性, jinfo 15008
因篇幅所限,只展示部分结果,如下所示:
结果中含有Java系统属性(System Properties)和JVM参数(VM Flags)。
(2)根据进程ID查询系统属性(选项:-sysprops)命令如下:
#查看系统属性 jinfo -sysprops 15008
运行结果如下,篇幅原因只展示部分结果,如下所示:
通过Java代码获取系统属性如下代码清单所示:
运行结果如下图所示:
通过比较两者结果一致。
(3)查看全部JVM参数配置(选项:flags),命令如下:
#查看全部JVM参数配置 jinfo -flags 15008
运行结果如下图所示:
可以看到里面包含初始堆大小、最大堆大小等参数配置。
(4)查看某个Java进程的具体参数的值(选项:-flag name),命令如下:
#查看JVM是否使用了ParallelGC垃圾收集器 jinfo –flag UseParallelGC 15008
运行结果如下图所示:
可以看到结果为-XX:+UseParallelGC,其中UseParallelGC前面的“+”表示已经使用,如果没有使用的话,用“-”表示。也可以查看某个参数的具体数值,比如查看新生代对象晋升到老年代对象的最大年龄,命令如下:
#新生代对象晋升到老年代对象的最大年龄 jinfo -flag MaxTenuringThreshold 15008
从结果可知新生代对象晋升到老年代对象的最大年龄是15。
5、jinfo用于修改JVM配置信息案例
(1)开启或者关闭对应名称的参数(-flag [±]name)(或者称为修改布尔类型的参数)。
首先查看是否开启输出GC日志的参数,如果GC日志参数是开启状态,那么使用jinfo命令关闭;如果GC日志参数是关闭状态,那么使用jinfo命令开启,命令如下:
对于布尔类型的JVM参数,不仅可以使用-flag [±]name的形式来进行值的改变,也可以使用-flag name=value的形式修改运行时的JVM参数。但是对value赋值必须是1或者0,1表示“+”,0表示“-”,如下所示:
(2)修改对应名称的参数(-flag name=value)(或者称为修改非布尔类型的参数)。
修改非布尔类型MaxHeapFreeRatio的值,命令如下:
运行结果如下图所示:
除了使用jinfo查看JVM配置参数之外,还有如下方式:
java -XX:+PrintFlagsInitial执行结果如下,展示部分结果:
java -XX:+PrintFlagsFinal执行结果如下,展示部分结果:
输出结果中包含五列。第一列表示参数的数据类型,第二列表示参数名称,第四列表示参数的值,第五列表示参数的类别。第三列“=”是参数的默认值,而“:=”表示参数被用户或者JVM赋值了。可以通过“java -XX:+PrintFlagsFinal |grep":="”命令查看哪些参数是被用户或者JVM赋值的。java -XX:+PrintFlagsInitial只展示了第三列为“=”的参数。
java -XX:+PrintCommandLineFlags执行结果如下,该参数输出被用户或者JVM设置过的详细的-XX参数的名称和值:
-XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
该参数的结果是java -XX:+PrintFlagsFinal的结果中带有“:=”的部分参数。可以通过该命令快捷地查看修改过的参数。
5、jmap:导出内存映像文件和内存使用情况
jmap(JVM Memory Map)用于生成JVM的内存转储快照,生成heapdump文件且可以查询finalize执行队列,以及Java堆与元空间的一些信息。jmap的作用并不仅仅是为了获取dump文件(堆转储快照文件,二进制文件),它还可以获取目标Java进程的内存相关信息,包括Java堆各区域的使用情况、堆中对象的统计信息、类加载信息等。
开发人员可以在控制台中输入命令“jmap-help”,查阅jmap工具的具体使用方式和标准选项配置。jmap的基本使用语法如下:
1、[ options ]选项说明
jmap工具[options]主要选项如下表所示:
这些参数和Linux下输入显示的命令多少会有一些不同,也受JDK版本的影响。其中选项-dump、-heap、-histo是开发人员在工作中使用频率较高的指令。
2、使用案例
(1)-dump选项:导出内存映像文件。
一般来说,使用jmap指令生成dump文件的操作算得上是最常用的jmap命令之一,将堆中所有存活对象导出至一个文件之中。执行该命令,JVM会将整个Java堆二进制格式转储到指定filename的文件中。live子选项是可选的,如果指定了live子选项,堆中只有存活的对象会被转储。
通常在写dump文件前会触发一次Full GC,所以dump文件里保存的都是Full GC后留下的对象信息。由于生成dump文件比较耗时,因此大家需要耐心等待,尤其是大内存镜像生成dump文件需要耗费更长的时间来完成。
如果想要浏览dump文件,大家可以使用jhat(Java堆分析工具)读取生成的文件,也可以使用可视化工具进行解读,比如MAT内存分析工具。获取dump文件有手动获取和自动获取两种方式。
手动获取的意思是当发现系统需要优化或者需要解决内存问题时,需要开发者主动执行jmap命令,导出dump文件,手动获取命令如下:
#手动获取堆内存全部信息 jmap -dump:format=b,file=<filename.hprof><pid> #手动获取堆内存存活对象全部信息 jmap -dump:live,format=b,file=<filename.hprof><pid>
代码清单如下所示,往堆内存中存放数据,然后导出dump文件。
运行程序,最终该程序会产生内存溢出,在执行过程中使用上述命令导出dump文件即可,运行结果如下图所示:
上述命令中,file表示指定文件目录,这里将文件放到D盘根目录,10692表示Java进程ID,结果如下图所示,dump1.hprof就是导出的结果文件。
当程序发生内存溢出退出系统时,一些瞬时信息都随着程序的终止而消失,而重现OOM问题往往比较困难或者耗时。若能在OOM时,自动导出dump文件就显得非常迫切。可以配置JVM参数“-XX:+HeapDumpOnOutOfMemoryError:”使程序发生OOM时,导出应用程序的当前堆快照。
依然使用上面代码,在启动程序之前,在idea中添加如下JVM参数配置:
-Xms60m -Xmx60m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/autoDump.hprof
添加参数如下图所示:
启动程序,运行结果如下图所示:
从结果可知,程序发生了内存溢出,此时dump文件自动生成到目标目录中,如下图所示:
(2)-heap选项:显示堆内存相关信息
命令如下:
#1520表示当前进程ID jmap -heap 1520
运行结果如下:
打印heap的概要信息、GC使用的算法、heap的配置和使用情况,可以判断当前堆内存使用情况以及垃圾回收情况。
可以看到最大堆大小为60MB,和前面的VM配置信息一致;新生代大小等于Eden区加From区加To的大小,总共为20MB,符合新生代和老年代比例大小为2:1;老年代大小为40MB,老年代使用率达到了99.53%,说明老年代空间是不足的。
(3)-hiso选项:显示堆中对象的统计信息:
#1520表示当前进程ID jmap -histo 1520
运行结果如下,由于篇幅原因,展示部分结果:
上面结果中,instances表示当前的实例数量;bytes表示对象占用的内存大小;classs name表示类名,按照内存大小逆序排列。
(4)-permstat选项:
该选项主要以ClassLoader为口径输出永久代的内存状态信息,仅对Linux和solaris平台有效。
(5)-finalizerinfo选项。
该选项主要用来显示F-Queue中等待Finalize线程执行finalize方法的对象,就是说查看堆积在finalizer队列中的对象。仅对Linux和solaris平台有效。
(6)-F选项
该选项用于当JVM进程对-dump选项没有任何响应时,可使用此选项强制执行生成dump文件。仅对Linux和solaris平台有效。
由于jmap将访问堆中的所有对象,为了保证在此过程中不被应用线程干扰,jmap需要借助安全点机制,让所有线程不改变堆中数据的状态。也就是说,由jmap导出的堆快照必定是安全点位置的。这可能导致基于该快照的分析结果存在偏差。例如,假设在编译生成的机器码中,某些对象的生命周期在两个安全点之间,那么:live选项将无法探知到这些对象。另外如果某个线程长时间无法跑到安全点,jmap将一直等待下去。与前面讲的jstat不同,垃圾收集器会主动将jstat所需要的摘要数据保存至固定位置中,而jstat只需要直接读取即可。
6、jhat:JDK自带堆分析工具
jhat(JVM Heap Analysis Tool)命令一般与jmap命令搭配使用,用于分析jmap生成的dump文件(堆转储快照)。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,用户可以在浏览器中查看分析结果。
使用了jhat命令,就启动了一个http服务,端口是7000,即通过访问http://localhost:7000/就可以在浏览器中查看结果。jhat命令在JDK9中已经被删除,官方建议用VisualVM代替。实际工作中一般不会直接在生产服务器使用jhat分析dump文件。
7、jstack:打印JVM中线程快照
jstack(JVM Stack Trace)用于生成JVM指定进程当前时刻的线程快照(Thread Dump),方便用户跟踪JVM堆栈信息。线程快照就是当前JVM内指定进程的每一条线程正在执行的方法堆栈的集合。
生成线程快照的作用是可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题,这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用jstack显示各个线程调用的堆栈情况。
在线程快照中,有下面几种状态,如下表所示:
其中线程的Deadlock、Waiting on condition、Waiting on monitor entry以及Blocked状态需要在分析线程栈的时候重点关注。
jstack的基本使用语法如下:
jstack [ option ] <pid>
1、[ options ]选项说明
jstack工具[options]主要选项如下表所示:
2、使用案例
代码清单如下演示了线程死锁,使用jstack命令观察线程状态。
上面例子很简单,启动了两个线程,分别获取对方的资源,如此造成死锁。下面启动程序,使用jstack命令查看线程状态,命令如下,其中1776是程序的进程ID。
jstack 1776
运行结果如下:
从上面结果中可以发现,Thread-1线程和Thread-0线程互相等待对方的资源,问题代码出现“com.yang.ThreadDeadLock$2.run()”行。在死锁情况出现时,可以很方便地帮助定位到问题。也可以通过Thread.getAllStackTraces()方法获取所有线程的状态,代码清单如下所示:
运行结果如下图所示,可以看到各个线程的状态:
8、jcmd:多功能命令行
在JDK1.7以后,新增了一个命令行工具jcmd。它是一个多功能的工具,可以用来实现前面除了jstat之外所有命令的功能,比如用它来导出堆、内存使用、查看Java进程、导出线程信息、执行GC、JVM运行时间等。jcmd拥有jmap的大部分功能,并且官方也推荐使用jcmd命令代替jmap命令。
至于jstat的功能,虽然jcmd复制了jstat的部分代码,并支持通过PerfCounter.print子命令来打印所有的Performance Counter,但是它没有保留jstat的输出格式,也没有重复打印的功能。
jcmd的基本使用语法如下表所示:
9、jstatd:远程主机信息收集
之前的指令只涉及监控本机的Java应用程序,而在这些工具中,一些监控工具也支持对远程计算机的监控(如jps、jstat)。为了启用远程监控,则需要配合使用jstatd工具。
命令jstatd是一个RMI服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd服务器将本机的Java应用程序信息传递到远程计算机。执行原理如下图所示:
直接打开jstatd服务器可能会抛出访问拒绝异常,这是因为jstatd程序没有足够的权限,如下图所示:
10、小结
介绍了JDK自带的命令行工具及其常用参数。当Java应用和服务出现莫名的卡顿、CPU飙升等问题时,通过JDK自带的状态监控命令和图形化工具,可以非常方便地分析对应进程的JVM状态,进而定位问题并解决问题。
到此这篇关于JVM 命令行工具的使用的文章就介绍到这了,更多相关JVM 命令行工具内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!