概述
给一个系统定位问题的时候,知识、经验 是关键基础,数据 是依据,工具是运用知识处理数据的手段。这里说的数据包括但不限于运行日志、异常堆栈、GC 日志、线程快照(threaddump / javacore 文件)、堆转储快照(heapdump / hprof 文件)等。恰当使用虚拟机故障处理、分析的工具可以提升我们分析数据、定位并解决问题的效率。
基础故障处理工具
相信大家都对 JDK 和 JRE 的区别有所了解:JRE(Java Runtime Environment)是 Java 运行时环境,包括了 JVM 和一些运行时必要的核心类库;JDK(Java Development Kit)是一个功能齐全的开发包,包含了 JRE 以及许许多多的开发工具,如 jdb、javap、jps 等等。打开 JDK 的 bin 文件目录就可以看到这许许多多的工具。
jps: 虚拟机进程状况工具
jps(JVM Process Status Tool )从名称就可以看出来,和 Linux 的 ps 命令很像。事实上,它的功能也和 ps 命令类似:可以列出正在运行的虚拟机进程 ,并显示虚拟机执行主类(Main()函数所在的类)名称以及这些进程的本地虚拟机唯一 ID(LVMID,Local Virtual Machine Identifier)。注:对于本地虚拟机进程来说,LVMID 和操作系统的进程 ID 是一致的。
jps 的命令格式为:jps [options] [hostid]
下表列出了 jps 的常用选项。
选项 | 作用 |
---|---|
-q | 只输出 LVMID,省略主类的名称 |
-m | 输出虚拟机进程启动时传递给主类main()的参数 |
-l | 输出主类的全名,如果进程执行的是 JAR 包,则输出 JAR 路径 |
-v | 输出虚拟机进程启动时的 JVM 参数 |
jstat: 虚拟机统计信息监视工具
jstat(JVM Statistics Monitoring Tool )是用于监视虚拟机各种运行状态信息 的命令行工具。它可以显示进程中的类加载、内存、垃圾收集、即时编译等运行时数据,在没有 GUI 图形界面的服务器上,它是运行期定位虚拟机性能问题的常用工具。
jstat 的命令格式为:jstat [option vmid [interval[s|ms] [count]]
其中 vmid 是进程 ID,interval 和 count 代表查询间隔和次数,如果省略说明只查一次。假设需要每 250 毫秒查询一次进程 2764 的垃圾收集状况,一共查询 20 次 ,那命令应该是:jstat -gc 2764 250 20
。
下表列出了 jstat 的常用选项。
选项 | 作用 |
---|---|
-class | 监视类加载、卸载数量、总空间以及类装载所耗费的时间 |
-gc | 监视Java堆状况,包括各个区的容量、已用空间、垃圾收集时间合计等信息 |
-gcutil | 监视内容与-gc基本相同,但主要关注已使用空间占总空间的百分比 |
-gcnew | 监视新生代垃圾收集状况 |
-gcold | 监视老年代垃圾收集状况 |
-compiler | 输出即时编译器编译过的方法、耗时等信息 |
-printcompilation | 输出已经被即时编译的方法 |
下图给出 jstat -gcutil 2796
命令的输出例子:
查询结果表明:Eden 区(E)、两个 Survivor 区(S0 和 S1)都是空的,老年代(O)和元空间(M)分别使用了 0.69% 和 76.93% 的空间。程序运行共发生了 625 次 Minor GC(YGC),总耗时 0.618 秒(YGCT)。所有 GC 总耗时(GCT)为 0.577 秒。
jinfo: Java 配置信息工具
jinfo(Configuration Info for Java)的作用是实时查看和调整虚拟机各项参数 ,使用 jps 命令的 -v 参数可以查看虚拟机启动时显式指定的参数列表,但如果想知道未被显式指定的参数的系统默认值,除了去找资料外,就只能使用 jinfo 的 -flag 选项进行查询了。
JDK 6 之后,jinfo 加入了运行期修改参数的能力(无需重启),可以使用 -flag[+|-]name 或者 -flag name=value 修改一部分运行期可写的虚拟机参数值。
jinfo 的命令格式为:jinfo [option] vmid
,例如查看并修改 PrintGC 参数(4948 是通过 jps 查得的 LVMID):
jmap: Java 内存映像工具
jmap(Memory Map for Java)命令用于生成堆转储快照 ,即 heapdump文件。另外,该命令还可以查询 finalize 执行队列、Java 堆和方法区的详细信息,如空间使用率、当前用的是哪种收集器等。
jmap 的命令格式为:jmap [option] vmid
,下表列出了 jstat 的常用选项。
这里对 -dump 命令举个具体的例子:jmap -dump:format=b,file=heapdump.bin 3500
,表示对 LVMID 为 3500 的虚拟机进程中的所有对象生成 dump 文件,文件名是 heapdump.bin,格式是二进制。
jstack: Java 堆栈跟踪工具
jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照 (一般称为 threaddump 或者 javacore 文件)。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合 ,生成线程快照的主要目的是定位线程出现长时间停顿的原因 ,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。线程出现停顿的时候通过 jstack 来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做些什么事情,或者等待着什么资源。
jstack 的命令格式为 jstack [option] vmid
,下表列出了 jstack 的常用选项。
选项 | 作用 |
---|---|
-F | 当正常输出的情趣不被响应时,强制输出线程堆栈 |
-l | 除堆栈外,显示关于锁的附加信息 |
-m | 如果调用到本地方法的话,可以显示 C/C++ 的堆栈 |
下面的代码是一段简单的死锁代码,我们尝试用 jstack 去分析:
java
public class DeadLockTest {
private static Lock lock1 = new ReentrantLock();
private static Lock lock2 = new ReentrantLock();
public static void deathLock() {
Thread t1 = new Thread() {
@Override
public void run() {
try {
lock1.lock();
System.out.println(Thread.currentThread().getName() + " get the lock1");
Thread.sleep(1000);
lock2.lock();
System.out.println(Thread.currentThread().getName() + " get the lock2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
try {
lock2.lock();
System.out.println(Thread.currentThread().getName() + " get the lock2");
Thread.sleep(1000);
lock1.lock();
System.out.println(Thread.currentThread().getName() + " get the lock1");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//设置线程名字,方便分析堆栈信息
t1.setName("mythread-1");
t2.setName("mythread-2");
t1.start();
t2.start();
}
public static void main(String[] args) {
deathLock();
}
}
通过命令 jstack -l vmid
得到结果,可以清晰地看到死锁信息:
可视化故障处理工具
JDK 中除了覆盖大量命令行工具外,还提供了几个功能集成度更高的可视化工具,用户可以使用这些可视化工具以更加便捷的方式进行进程故障诊断和调试工作。主要包括 JConsole、JHSDB、VisualVM、JMC四个。关于可视化工具的使用,这里不做过多介绍,有兴趣的朋友可以自行尝试使用。