1. 概述
性能诊断是软件工程师在日常工作中需要经常面对和解决的问题,在用户体验至上的今天,解决好应用的性能问题能带来非常大的收益。
Java作为最流行的编程语言之一,其应用性能诊断一直受到业界广泛关注。可能造成Java出现性能问题的因素非常多,例如线程控制、磁盘读写、数据库访问、网络I/O,垃圾收集等。想要定位这些问题,一款优秀的性能诊断工具必不可少。
2. jps:查看正在运行的java进程
2.1 基本情况
jps(Java Process Status):
显示指定系统内所有的HotSpot虚拟机进程,可用于查询正在运行的虚拟机进程。
说明:对于本地的虚拟机进程来说,进程的本地虚拟机ID与操作系统的进程ID是一致的,是唯一的。
2.2 基本语法
jps [options] [hostid]
我们还可以通过追加参数,来打印额外的信息
1)基本语法之:options参数
-
-q: 仅仅显示LVMID(local virtual machine id),即本地虚拟机唯一id。不显示主类的名称等
-
-l:输出应用程序主类的全类名 或 如果进程执行的是jar包,则输出jar包完整路径
-
-m:输出虚拟机进程启动时传递给主类main()的参数
-
-v:列出虚拟机进程启动时的JVM参数
上述命令可以组合使用
补充:
如果某Java进程关闭了默认开启的UsePerfData参数(即使用参数 -XX: -UsePerfData),那么jps命令将无法探知该Java进程。
2)基本语法之: hostid参数
RMI注册表中注册的主机名。
如果想要远程监控主机上的java程序,需要安装 jstatd。
对于具有更严格的安全实践的网络场所而言,可能是用一个自定义的策略文件来显示对特定的可信主机或网络的访问,尽管这种技术容易受到ip地址欺诈攻击。
对于安全问题无法使用一个定制的策略文件来处理,那么最安全的操作是不允许jstatd服务器,而是在本地使用jstat和jps工具。
3. jstat:查看JVM统计信息
2.1 基本情况
jstat(JVM Statistics Monitoring Tool):用户监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。
在没有GUI图形页面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄漏问题。
2.2 基本语法
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
1)基本语法之:options参数
-
类装载相关的:
-
-class:显示ClassLoader的相关信息:类的装载、卸载数量、总空间、类装载所消耗的时间等
-
-
垃圾回收相关的:
-
-gc:显示与GC相关的堆信息。包括Eden区、两个Survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息
-
-gccapacity:显示内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间
-
-gcutil:显示内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比
-
-gccause:与-gcutil功能相同,但输出主要关注已使用空间占总空间的百分比
-
-gcnew:显示新生代GC状况
-
-gcnewcapacity:显示内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间
-
-gcold:显示老年代GC情况
-
-gcoldcapacity:显示内容与-gcold基本相同,输出主要关注使用到的最大、最小空间
-
-gcpermcapacity:显示永久代使用到的最大、最小空间
-
-
JIT相关的:
-
-compiler:显示JIT编译器编译过的方法、耗时等信息
-
-printcompilation:输出已经被JIT编译的方法
-
2)基本语法之:interval参数
用于指定输出统计数据的周期,单位为毫秒
3)基本语法之:count参数
用户指定查询的总次数
4)基本语法之:t参数
可以在输出信息前加一个Timestamp列,显示程序的运行时间,单位为秒
5)基本语法值:h参数
可以指定在周期性数据输出时,输出多少行数据后输出一个表头信息
2.3 -gc 输出的相关数据
4. jinfo:查看和修改JVM配置参数
4.1 基本情况
查看虚拟机配置参数信息,也可用于调整虚拟机的配置参数。
在很多情况下,Java应用程序不会指定所有的Java虚拟机参数。而此时,开发人员可能不知道某一个具体的Java虚拟机参数的默认值。在这种情况下,可能需要通过查找文档来获取某个参数的默认值。这个查找过程可能是非常艰难的。但有了jinfo工具,开发人员可以很方便的找到Java虚拟机参数的当前值。
4.2 基本语法
jinfo [options] pid
说明:java进程ID必须要加上
选项 | 选项说明 |
---|---|
no option | 输出全部的参数和系统属性 |
-flag name | 输出对应名称的参数 |
-flag [+-]name | 开启或者关闭对应名称的参数,只有被标记为manageable的参数才可以动态修改 |
-flag name=value | 设定对应名称的参数 |
-flags | 输出全部的参数 |
-sysprops | 输出系统属性 |
4.3 修改参数
jinfo不仅可以查看运行时某一个Java虚拟机参数的实际取值,甚至可以在运行时修改部分参数,并使之立即生效。
但是,并非所有参数都支持动态修改。参数只有被标记为manageable的flag可以被试试修改。
查看被标记为manageable的参数命令:
java -XX:+PrintFlagsFinal -version | grep manageable (grep为linux命令)
5. jmap:导出内存映像文件&内存使用情况
5.1基本情况
jmap(JVM Memory Map):作用一方面是获取dump文件(堆转储快照文件),它还可以获取目标Java进程的内存相关信息,包括Java堆各区域的使用情况、堆中对象的统计信息、类加载信息等。
5.2 基本语法
-
jmap [option] <pid>
-
jmap [option] <executable <core>
-
jmap [option] [server_id@]<remote server IP or hostname>
其中option包括:
选项 | 作用 |
---|---|
-dump | 生成dump文件 |
-finalizerinfo | 以ClassLoader为统计口径输出永久代的内存状态信息 |
-heap | 输出整个堆空间的详细信息 |
-histo | 输出堆空间中对象的统计信息,包括类、实例数量和合计容量(仅Linux\solaris平台使用) |
-permstat | 以ClassLoader为统计口径输出永久代的内存状态信息(仅Linux\solaris平台使用) |
-F | 当虚拟机进程对-dump进程没有任何响应时,强制执行生成dump文件(仅Linux\solaris平台使用) |
5.3 导出dump文件
手动:
-
jmap -dump:format=b,file=<filename.hprof> <pid>
-
jmap -dump:live,format=b,file=<filename.hprof> <pid> 只会导出存活对象,文件大小不会很大,推荐该方法
自动:
当程序发生OOM退出系统时,一些瞬时信息都随着程序的终止而消失,而重现OOM问题往往比较困难或耗时。此时若能在OOM时,自动导出dump文件就显得非常迫切
-
-XX:+HeapDumpOnOutOfMemoryError 当程序发生OOM时,导出应用程序的当前堆快照
-
-XX:HeapDumpPath=<filename.hprof> 可以指定堆快照的保存位置
5.4 自动导出dump演示
我们写一段java代码,并设置启动JVM命令
-Xms60m -Xmx60m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\m.hprof
public static void main(String[] args) {
ArrayList<byte[]> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
byte[] arr = new byte[1024 * 100];//100KB
list.add(arr);
try {
Thread.sleep(60);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
一段时间后,发现在我们制定的路径D盘下,生成了一个m.hprof的dump文件。
我们可以使用图形化工具来打开它,下面我们用JProfile打开。
6. jhat:JDK自带堆分析工具
Sun JDK提供的jhat命令与jmap命令搭配使用,用户分析jmap生成的heap dump文件。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,用户可以在浏览器中查看分析结果。
使用了jhat命令,就启动了一个http服务,端口是7000,即 http://localhost:7000/
说明:jhat命令在JDK9中已经被删除,官方建议用VisualVM代替
7. jstack:打印JVM中线程快照
7.1 基本情况
jstack(JVM Stack Trace):用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。
作用:可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题。这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用jstack显示各个线程调用的堆栈情况。
在thread dump中,要留意下面几种状态
-
死锁,DeadLock(重点关注)
-
等待资源,Waiting on condition(重点关注)
-
等待获取监视器,Waiting on monitor entry(重点关注)
-
阻塞,Blocked(重点关注)
-
执行中,Runable
-
暂停,Suspended
7.2 基本语法
jstack [option] pid
选项 | 输出说明 |
---|---|
no option | 正常输出线程堆栈 |
-F | 当正常输出的请求不被响应时,强制输出线程堆栈 |
-l | 除堆栈外,显示关于锁的附加信息 |
-m | 如果调用到本地方法的话,可以显示C/C++的堆栈 |
-h | 帮助操作 |
7.3 死锁模拟分析
我们写一段死锁代码:
public class ThreadDeadLock {
public static void main(String[] args) {
StringBuilder s1 = new StringBuilder();
StringBuilder s2 = new StringBuilder();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
Map<Thread, StackTraceElement[]> all = Thread.getAllStackTraces();//追踪当前进程中的所有的线程
Set<Map.Entry<Thread, StackTraceElement[]>> entries = all.entrySet();
for(Map.Entry<Thread, StackTraceElement[]> en : entries){
Thread t = en.getKey();
StackTraceElement[] v = en.getValue();
System.out.println("【Thread name is :" + t.getName() + "】");
for(StackTraceElement s : v){
System.out.println("\t" + s.toString());
}
}
}
}).start();
}
}
然后进行jstack分析
可以发现Thread-1和Thread-0处于Blocked状态
jstack同时也分析出了这两个线程为死锁状态。
8. jcmd多功能命令行
8.1 基本情况
在JDK1.7以后,新增了一个命令行工具jcmd。
它是一个多功能的工具,可以用来实现前面除了jstat之外所有命令的功能。比如:用它来导出堆、内存使用、查看Java进程、导出线程信息、执行GC、JVM运行时间等。
jcmd拥有jmap的大部分功能,并且Oracle官方也推荐使用jcmd代替jmap命令。
8.2 基本语法
-
jcmd -l:列出所有的JVM进程
-
jcmd pid help:针对指定的进程,列出所有支持的命令
-
jcmd pid 具体命令:显示指定进程的指令命令的数据