2020-heibaiying-JVM性能监控之命令行工具

JVM性能监控之命令行工具

一、简介

JDK安装目录的 bin 文件夹下,除了提供有 javacjava 这两个常用的编译和运行工具外,还提供了一系列命令行工具用于JVM的性能监控和故障诊断,常用的命令如下:

二、jps

jps(JVM Process Status Tool)用于列出正在运行的虚拟机进程的主类名称和LVMID(Local Virtual Machine Identifier,本地虚拟机唯一标识,这里得到的LVMID是进行后续其它查询的基础。示例如下:

C:/Users>jps
10848 Main
14560 Jps
7040 Launcher
11572
9492 DeadLockTest
7868 JConsole

可选参数有 -v ,用于输出虚拟机进程启动时的JVM参数。

三、jstat

jstat(JVM Statistics Monitoring Tool)用于监视虚拟机的运行状态。使用格式如下:

jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

其中 option 的所有可选值如下:

选项 作用
-class 监视类加载、卸载数量、总空间以及类装载所耗费的时间
-gc 监视Java堆状况,包括Eden区、2Survivor区、老年代的容量、已用空间、垃圾收集时间等信息
-gccapacity -gc基本相同,但主要关注的是Java堆各个区域使用到的最大、最小空间
-gcutil -gc基本相同,但主要关注的是已使用空间占总空间的百分比
-gccause -gcutil基本相同,但是会额外输出上一次垃圾回收的原因
-gcnew 监视新生代垃圾回收的状况
-gcnewcapacity -gcnew基本相同,但主要关注的是使用到的最大、最小空间
-gcold 监视老年代垃圾回收的状况
-gcoldcapacity -gcold基本相同,但主要关注的是使用到的最大、最小空间
-compiler 输出即时编译器编译过的方法、耗时等信息
-printcompilation 输出已经被即时编译的方法

命令行中的 interval 表示监控的时间间隔,count 表示监控次数。示例如下:

jstat -gc 9492 3s 5 # 每3s输出一次,一共输出5次

输出信息中各个参数含义分别如下:

  • S0Csurvivor 0的容量大小,单位kB
  • S1Csurvivor 1的容量大小,单位kB
  • S0Usurvivor 0已使用的空间大小,单位kB
  • S1Usurvivor 1已使用的空间大小,单位kB
  • ECEden区的容量大小,单位kB
  • EUEden区已使用的空间大小,单位kB
  • OC:老年代的容量大小,单位kB
  • OU:老年代已使用的空间大小,单位kB
  • MCMetaspace容量大小,单位kB
  • MUMetaspace已使用的空间大小,单位kB
  • CCSC:压缩类的空间大小,单位kB
  • CCSU:压缩类已使用的空间大小,单位kB
  • YGC:年轻代垃圾回收的次数;
  • YGCT: 年轻代垃圾回收所消耗的时间;
  • FGC:老年代垃圾回收的次数;
  • FGCT:老年代垃圾回收所消耗的时间;
  • GCT:垃圾回收所消耗的总时间。

以上是option-gc 时的输出结果,不同option的输出结果是不同的,所有输出结果及其参数解释可以参考官方文章: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html

四、jinfo

jinfo(Configuration Info for Java)的作用是实时查看和调整虚拟机的各项参数。使用格式如下:

jinfo [option] <pid>

其中 option 支持以下可选项:

  • -flag name :输出指定的虚拟机参数的值;
  • -flag [+|-]name :启用或禁用指定名称的虚拟机参数;
  • -flag name=value :设置虚拟机参数的值;
  • -flags :以键值对的方式输出JVM的相关属性;
  • -sysprops:以键值对的方式输出Java相关的系统属性。

示例如下:

jinfo -flags 13604
jinfo -flag CMSInitiatingOccupancyFraction 13604

五、jmap

jmap(Memory Map for Java)命令主要用于生成堆转储快照(一般称为heapdumpdump文件。除此之外,它还可以用来查询finalize执行队列、Java堆和方法区的详细信息,如空间使用率、当前使用的收集器等。 使用格式如下:

jmap [option] <pid>

其中 option 支持以下可选项:

选项 作用
-dump:[live,]format=b,file= 生成Java堆转储快照,其中live用于指明是否只dump出存活的对象
-finalizerinfo 显示在F-Queue中等待Finalizer线程执行finalize方法的对象。只在Linux/Solaris平台下有效
-heap 显示Java堆详细信息,如使用哪种回收器、参数配置、分代状况等。只在Linux/Solaris平台下有效
-histo[:live] 显示堆中对象的统计信息,包括类、实例数量、合计容量
-permstat ClassLoader为统计口径显示永久代内存状态。只在Linux/Solaris平台下有效
-F 当虚拟机进程堆-dump选项没有响应时,可使用这个选项强制生成dump快照。
只在Linux/Solaris平台下有效

示例如下:

jmap -dump:format=b,file=test.bin 3260

六、jhat

jhat(JVM Heap Analysis Tool)命令主要用来分析jmap生成的堆转储快照。 假设我们有如下一段程序:

public class StackOverFlowTest {

    private static List<StackOverFlowTest> list = new ArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        while (true) {
            list.add(new StackOverFlowTest());
            Thread.sleep(10); //因为只是演示,所以休眠一下,避免生成的堆转储文件过大,导致分析时间过长
        }
    }
}

其最终会抛出 java.lang.OutOfMemoryError: Java heap space 异常,意味着在JVM堆上发生了内存溢出。在程序运行期间,我们可以使用上面的jmap命令生成堆转储快照,并使用jhat命令进行分析:

jhat命令最终的分析结果会以网页的方式进行提供,端口为7000,界面如下:

jhat分析的结果并不够直观,因此我们还可以借助第三方工具来分析堆转储快照,这里以JProfiler为例,该软件可以直接从官网下载并安装,安装完成后,点击 session 选项卡,并使用 Open Snapshot 打开jmap命令生成的堆转储快照:

之后程序会自动进行分析,分析结果如下:

通过以上可视化的统计结果,我们就可以很快定位到导致内存溢出的原因。

七、jstack

jstack(Stack Trace for Java)命令用于生成虚拟机的线程快照(一般称为threaddump或者javacore文件。线程快照就是每一条线程正在执行的方法堆栈的集合,线程快照可以用于定位线程长时间停顿的原因,如死锁、死循环和长时间挂起等。其使用格式如下:

 jstack -F [-m] [-l] <pid>

各选项的作用如下:

选项 作用
-F 当正常输出的请求不被响应时,强制输出线程堆栈
-m 除堆栈外,显示关于锁的附加信息
-l 如果有调用本地方法的话,则可以显示C/C++的堆栈

假设我们的程序中存在如下死锁:

public class DeadLockTest {

    private static final String a = "a";
    private static final String b = "b";

    public static void main(String[] args) {
        new DeadLockTest().deadlock();
    }

    private void deadlock() {
        new Thread(() -> {
            synchronized (a) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b) {
                }
            }
        }).start();
        new Thread(() -> {
            synchronized (b) {
                synchronized (a) {
                }
            }
        }).start();
    }
}

此时使用jstack分析就能很快的定位到问题所在,示例如下:

jstack 8112

输出结果如下:

从输出中结果中可以看出,出现了一个死锁,该死锁由线程Thread-0Thread-1导致,原因是Thread-0锁住了对象 <0x00000000d6d8d610> ,并尝试获取 <0x00000000d6d8d640> 对象的锁;但是Thread-0却恰恰相反,锁住了对象 <0x00000000d6d8d640> ,并尝试获取 <0x00000000d6d8d610> 对象的锁,由此导致死锁。

参考资料

  • 主要参考自:周志明.深入理解Java虚拟机(第3版).机械工业出版社, 2019-12 ,想要深入了解虚拟机的话,推荐阅读原书。
  • 官方文档:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/s11-troubleshooting_tools.html#sthref327
上一页