文章目录
- 1、概述
- 2、jps:查看正在运行的Java进程
- 3、jstat:查看JVM统计信息
- 4、jinfo:实时查看和修改JVM配置参数
- 5、jmap:导出内存映像文件和内存使用情况
- 6、jhat:JDK自带堆分析工具
- 7、jstack:打印JVM中线程快照
- 8、jcmd:多功能命令行
- 9、jstatd:远程主机信息收集
- 10、小结
性能问题是软件工程师在日常工作中需要经常面对和解决的问题,在用户体验至上的今天,解决好应用的性能问题能带来非常大的收益。工欲善其事,必先利其器,想要解决性能相关问题,必须要有比较好的性能诊断工具。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的基本使用语法如下:
bash
jps [ options ] [ hostid ]
1、jps命令[options]选项说明:
2、[hostid]说明:
hostid表示目标主机的主机名或IP地址,如果省略该参数,则目标主机为本地主机。如果想要远程监控主机上的Java程序,需要安装jstatd。对于网络安全要求非常严格的场所,需要自定义策略文件来满足对特定的主机或网络的访问,但是这种技术容易受到IP地址欺诈攻击。如果由于安全问题无法通过定制的策略文件处理,那么最安全的操作是在主机本地使用jstat和jps工具。
3、使用案例:
Linux上启动Tomcat(一种Web应用服务器),然后在Linux上面使用ps命令查看Tomcat进程ID使用,如下所示:
bash
ps -ef | grep"tomcat"
运行结果如下图所示:
可以看到进程ID是1224。使用jps -l命令查看,如下图上所示:
3、jstat:查看JVM统计信息
jstat(JVM Statistics Monitoring Tool)用于收集JVM各方面的运行数据,显示本地或远程JVM进程中的类装载、内存、垃圾收集、JIT编译等运行数据。在没有图形用户界面时,只提供了纯文本控制台环境的服务器上,它是运行期定位JVM性能问题的首选工具。常用于检测垃圾回收问题以及内存泄漏问题。它的功能非常强大,可以通过它查看堆信息的详细情况。
jstat的基本使用语法如下:
bash
jstat -<option>[-t] [-h<lines>] <vmid>[<interval>[<count>]]
使用下面的命令可以查看jstat相关参数:
bash
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的参数。
```bash
java -XX:+PrintFlagsFinal -version | grep manageable
```
运行结果如下图所示:

jinfo的基本使用语法如下:
```bash
jinfo [ options ] pid
```
**1、\[ options \]选项说明**
jinfo工具options主要选项如下表所示:

**2、pid说明**
Java进程ID,必须要加上。
**3、使用案例**
由于jinfo可以查看JVM配置信息,也可用于调整JVM的配置参数,下面分两类案例分别讲解如何使用。
**4、jinfo用于查看JVM配置信息案例**

程序很简单,只需要保证程序在执行状态即可。首先使用jps命令查看进程ID,如下所示:
```bash
#查看进程ID
jps -l
```
执行结果如下:
```bash
C:\Users\Administrator>jps -l
15008 com.yang.ScannerTest
14820 sun.tools.jps.Jps
```
上面出现两个结果,ScannerTest程序的进程ID是15008,另外一个进程ID表示jps命令本身的进程。下面使用jinfo命令来查看JVM配置参数,命令如下:
(1)根据进程ID查询全部参数和系统属性:
```bash
#查看全部参数和系统属性,
jinfo 15008
```
因篇幅所限,只展示部分结果,如下所示:

结果中含有Java系统属性(System Properties)和JVM参数(VM Flags)。
(2)根据进程ID查询系统属性(选项:-sysprops)命令如下:
```bash
#查看系统属性
jinfo -sysprops 15008
```
运行结果如下,篇幅原因只展示部分结果,如下所示:

通过Java代码获取系统属性如下代码清单所示:

运行结果如下图所示:

通过比较两者结果一致。
(3)查看全部JVM参数配置(选项:flags),命令如下:
```bash
#查看全部JVM参数配置
jinfo -flags 15008
```
运行结果如下图所示:

可以看到里面包含初始堆大小、最大堆大小等参数配置。
(4)查看某个Java进程的具体参数的值(选项:-flag name),命令如下:
```bash
#查看JVM是否使用了ParallelGC垃圾收集器
jinfo --flag UseParallelGC 15008
```
运行结果如下图所示:

可以看到结果为-XX:+UseParallelGC,其中UseParallelGC前面的"+"表示已经使用,如果没有使用的话,用"-"表示。也可以查看某个参数的具体数值,比如查看新生代对象晋升到老年代对象的最大年龄,命令如下:
```bash
#新生代对象晋升到老年代对象的最大年龄
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参数的名称和值:
```bash
-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文件,手动获取命令如下:
```bash
#手动获取堆内存全部信息
jmap -dump:format=b,file=