JVM监控及诊断工具-命令行篇


1 概述

性能诊断是软件工程师在日常工作中经常面对和解决的问题,在用户体验至上的今天,解决好应用软件的性能问题能带来非常大的收益。
Java作为最流行的编程语言之一,其应用性能诊断一直受到业界广泛关注。可能造成Java应用出现性能问题的因素非常多,例如线程控制、磁盘读写、数据库访问、网络I/O、垃圾收集等。想要定位这些问题,一款优秀的性能诊断工具必不可少。
体会1:使用数据说明问题,使用知识分析问题,使用工具处理问题
体会2:无监控、不调优!

简单命令行工具

在我们刚接触java学习的时候,大家肯定最先了解的两个命令就是 javac,java,那么除此之外,还有没有其他的命令可供我们使用呢?我们进入jdk的bin目录,发现还有一系列辅助工具。这些辅助工具用来获取目标JVM不同方面、不同层次的信息,帮助开发人员很好的解决Java应用程序的一些疑难杂症。

对应源码地址:https://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/jdk.jcmd/share/classes/sun/tools

2 jps:查看正在运行的java进程

2.1 基本情况

jps:Java Process Status

显示指定系统内所有的HotSpot虚拟机进程(查看虚拟机进程信息),可用于查询正在运行的虚拟机进程。

说明:对于本地虚拟机进程来说,进程的本地虚拟机ID与操作系统的进程ID是一致的,是唯一的。

2.2 测试

public class ScannerTest {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String info = scanner.next();
    }
}

每次jps都是一个新的进程

2.3 基本语法

jps的基本语法:jps [options] [hostid]

[options]:

-q:仅仅显示LVMID(local virtual machine id),即本地虚拟机唯一id。不显示主类的名称等。
-l:输出应用程序主类的全类名 或 如果执行的是jar包,则输出jar包的完整路径
-m:输出虚拟机进程启动时传递给主类main()的参数
-v:列出虚拟机进程启动时的JVM参数。比如:-Xms100m -Xmx100m是启动程序指定的JVM参数
说明:以上参数可以综合使用。
补充:如果某Java进程关闭了默认开启的UsePerfData参数(即使用参数-XX:-UsePerfData),那么jps命令(以及下面介绍的jstat)将无法探知该Java进程

在IDEA中配置参数如下:

在idea配置vm参数

[hostid]:

RMI注册表中注册的主机名
如果想要远程监控主机上的java程序,需要安装jstatd
对于具有更严格的安全实践的网络场所而言,可以使用一个自定义的策略文件来显式对特定的可信主机或网络的访问,尽管这种技术很容易受到IP地址欺诈攻击。
如果安全问题无法使用一个定制的策略文件来处理,那么最安全的操作是不运行jstatd服务器,而是本地使用jstat和jps工具。

3 jstat:查看jvm的统计信息

3.1 基本情况
jstat(JVM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令工具。它可以显示本地或者远程虚拟机中的类装载、内存、垃圾收集、JIT编译等运行数据。
在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期间定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄露问题。

官网:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html

3.2  基本语法

jstat -

测试

public class ScannerTest {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String info = scanner.next();
    }
}

option参数

选项option可以由以下值构成:

类装载相关的:

-class:显示ClassLoader的相关信息:类的装载、卸载数量、总空间、类装载所消耗的时间等

垃圾回收相关的:

测试代码:

/**
 * -Xms60m -Xmx60m -XX:SurvivorRatio=8
 */
public class GCTest {
    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(120);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

-gc:显示与GC相关的堆信息。包括Eden区,两个Survivor区、老年代、永久代的用量、已用空间、GC时间合计等信息。

-XX:NewRatio=2  因此新生代和老年代比例为1:2,新生代20M,老年代40M

-XX:SurvivorRatio=8 S0:S1:E=1:1:8,S0和S1都为2M,Eden为16M

各参数含义如下:

-gccapacity:显示内容与-gc基本相同,但输出主要关注Java堆各个区域使用的最大、最小空间。

-gcutil:显示内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比。

-gccause:与-gcutil功能一样,但是会额外输出导致最后一次或当前正在发生的GC产生的原因。

-gcnew:显示新生代GC状况

-gcnewcapacity:显示内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间。

-gcold:显示老年代GC状况

-gcoldcapacity:显示内容与-gcold基本相同,输出主要关注使用到的最大、最小空间。

-gcpermcapacity:显示永久代用到的最大、最小空间

JIT相关的:

-compiler:显示JIT编译器编译过的方法、耗时等信息

-printcompilation:输出已被JIT编译的方法

interval参数

用于指定输出统计数据的周期,单位为毫秒。即:查询间隔

count参数

用于指定查询的总次数

-t参数

可以在输出信息前面加上一个Timestamp列,显示程序的运行时间。单位:秒
经验:我们可以比较Java进程的启动时间以及总GC时间(GCT列),或者两次测量的时间间隔以及总GC时间的增量,来得出GC时间栈运行时间的比例。如果该比例超过20%,则说明目前堆的压力较大;如果该比例超过90%,则说明堆里几乎没有可用空间,随时都可能抛出OOM异常。
-h参数

可以在周期性数据输出时,输出多少行数据后输出一个表头信息

3.3 补充

jstat还可以用来判断是否出现内存泄漏。

第1步:在长时间运行的Java程序中,我们可以运行jstat命令连续获取多行性能数据,并取这几行数据中的OU列(即已占用的老年代内存)的最小值。

第2步:然后,我们每隔一段较长的时间重复一次上述操作,来获取多组OU最小值。如果这些值呈现上涨趋势,则说明该Java程序的老年代内存已使用量不断上涨,这意味着无法回收的对象在不断增加,因此有可能存在内存泄露。

4 jinfo:实时查看和修改JVM配置参数

4.1 基本情况

jinfo(Configuration Info For Java):查看虚拟机配置参数信息,也可以用于调整虚拟机的配置参数

在很多情况下,Java应用程序不会指定所有的Java虚拟机参数。而此时,开发人员可能不知道某一个具体的Java虚拟机参数默认值。在这种情况下,可能需要查找文档获取某个参数的默认值。这个查找过程可能是非常艰难的。但是有了jinfo工具,开发人员可以很方便地找到Java虚拟机参数的当前值。

官网 https://docs.oracle.com/en/java/javase/11/tools/jinfo.html   4.2 基本语法
jinfo [option] pid # pid:进程的ID,必须加上
[option]


测试代码
public class ScannerTest {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String info = scanner.next();
    }
}

查看

https://docs.oracle.com/en/java/javase/11/tools/jmap.html

5.2 基本语法

https://docs.oracle.com/en/java/javase/11/tools/jstack.html

在thread dump中,要留意下面几种状态
死锁,Deadlock(重点关注)
等待资源,Waiting on condition(重点关注)
等待获取监视器,Waiting on monitor entry(重点关注)
阻塞,Blocked(重点关注)
执行中,Runnable
暂停,Suspended

7.2 基本语法

jstack option pid

jstack远程管理的话,需要在远程程序的启动参数中增加:

-Djava.rmi.server.hostname=......
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8888
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false

代码:

/**
 * 演示线程的死锁问题
 */
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();
        }
    }
}

Java层面追踪当前进程中的所有的线程

public class AllStackTrace {
    public static void main(String[] args) {
        Map all = Thread.getAllStackTraces();
        Set> entries = all.entrySet();
        for (Map.Entry 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());
            }
        }
    }
}

8 jcmd:多功能的命令行

8.1 基本情况

在JDK1.7以后,新增了一个命令行工具jcmd
它是一个多功能的工具,可以用来实现前面除了jstat之外所有命令的功能。比如:用它来导出堆,内存使用,查看Java进程、导出线程信息、执行GC、JVM运行时间等。

官方文档:https://docs.oracle.com/en/java/javase/11/tools/jcmd.html

jcmd拥有jmap的大部分功能,并且在Oracle的官方网站上页推荐使用jcmd命令代替jmap命令。

8.2 基本语法

# 使用如下命令可以替换jps,即列出所有的JVM进程,加不加-l运行结果一样
jcmd -l
# 针对指定的进程,列出支持的所有命令
jcmd pid help
# 显示指定进程的指令命令的数据
jcmd pid 具体命令

9 jstatd:远程主机信息收集

之前的命令值涉及到监控本机的Java应用程序,而在这些工具中,一些监控工具也支持对远程计算机的监控(如jps、jstat)。为了启用远程监控,则需要配合使用jstatd工具。
命令jstatd是一个RMI服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd服务器将本机的Java应用程序传递到远程计算机。

JVM