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

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

  • 概述
  • jps:查看正在运行的JAVA进程
    • 概览
    • 基本用法
  • jstat:查看JVM统计信息
    • 基本情况
    • interval参数
    • count参数
    • -t参数
    • -h参数
    • options参数
  • jinfo:实时查看和修改JVM参数
    • 基本情况
    • 基本用法
      • 查看
      • 修改
    • 拓展
  • jmap:到处内存映像文件和内存使用情况
    • 基本情况
    • 基本语法
    • 导出内存映像文件(主要作用)
      • 手动的方式
        • jmap -dump:format=b,file=< filename.hprof >< pid >
        • jmap -dump:live,format=b,file=< filename.hprof > < pid >
      • 自动的方式
    • 显示堆内存相关信息
      • jmap -heap pid
      • jmap -histo pid
    • 其他作用
      • jmap -permstat pid
      • jmap -finalizerinfo
    • 小结
  • jhat:JDK自带的堆分析工具
    • 基本情况
    • 基本语法
  • jstack:打印JVM中线程快照
    • 基本情况
    • 基本用法
  • jcmd:多功能的命令行
    • 基本情况
    • 基本用法
  • jstatd:远程主机信息收集
  • 结语

概述

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

jps:查看正在运行的JAVA进程

概览

package priv.user.jvm;

import java.util.Scanner;

/**
 * Description: 利用扫描器的阻塞状态验证jps的使用
 * Author:江洋大盗
 * Date:2021/3/11 21:08
 */
public class JPSTest {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        scanner.next();
    }
}

JVM监控及诊断工具之命令行篇_第1张图片

基本用法

jps [ options ] [ hostid ]

options参数:

  • -q:仅仅显示LVMID(local virtual machine id),即本地虚拟机唯一id,不显示主类名称。JVM监控及诊断工具之命令行篇_第2张图片

  • -l:输出应用程序的全类名或者如果进程是jar包,则输出jar完整路径JVM监控及诊断工具之命令行篇_第3张图片

  • -m:输出虚拟机进程启动时传递给主类main()的参数JVM监控及诊断工具之命令行篇_第4张图片JVM监控及诊断工具之命令行篇_第5张图片

  • -v:列出虚拟机进程启动时的JVM参数,比如:-Xms20m -Xmx50mJVM监控及诊断工具之命令行篇_第6张图片
    JVM监控及诊断工具之命令行篇_第7张图片

注:以上参数可以组合使用
JVM监控及诊断工具之命令行篇_第8张图片
JVM监控及诊断工具之命令行篇_第9张图片

补充:如果Java某进程关闭了默认开启的UsePerfData参数(即使用参数:-Xx:-UsePerfData),那么jps指令以及下面介绍的jstat指令将无法探知该Java进程。

jstat:查看JVM统计信息

基本情况

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

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

查看命令相关参数:
jstat -h 或 jstat -help

interval参数

用于指定输出统计数据的周期,单位为毫秒,即查询间隔,不指定interval时默认打印一次
JVM监控及诊断工具之命令行篇_第10张图片

count参数

用于指定查询的总次数

JVM监控及诊断工具之命令行篇_第11张图片

-t参数

可以在输出信息上加一个Timestamp列,显示程序的运行时间,单位秒
JVM监控及诊断工具之命令行篇_第12张图片

-h参数

可以在周期性数据输出时,在输出多少行数据后输出一次表头信息
JVM监控及诊断工具之命令行篇_第13张图片

options参数

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

类装载相关的

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

垃圾回收相关的:

  • -gc:显示与gc相关的堆信息,包括Eden区,两个Survivor区,老年代,永久代等容量、已用空间、GC时间合计等信息。
  • -gccapacity:显示内容基本与 -gc 相同,但输出主要关注Java堆各个区域使用到的最大、最小的空间。
  • -gcutil:显示内容基本与 -gc 相同,但输出主要关注已用空间占总空间的百分比。
  • -gccause:显示内容与 -gcutil 相同,但是会额外输出导致最后一次或当前正在发生的GC的产生的原因。
  • -gcnew:显示新生代GC状况
  • -gcnewcapacity:显示内容与 -gcnew 基本相同,输出主要关注使用到的最大、最小空间。
  • -gcold:显示老年代GC状况

JIT相关的

  • -compiler:显示JIT编译器编译过的方法、耗时等信息
  • -printcompilation:输出已经被JIT编译过的方法

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

基本情况

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

基本用法

jinfo [ options ] pid
说明 ,java进程id必须要加上

【options参数】

选项 参数说明
no options 输出全部的参数和系统属性
-flag name 输出对应名称的参数
-flag [±]name 开启或者关闭对应名称的参数,只有被标记为manageable的参数才可以被动态修改
-flag name=value 设定对应名称的参数
-flags 输出全部参数
-sysprops 输出系统属性

查看

  • jinfo -sysprops PID:可以查看由System.getProperties()获取的参数JVM监控及诊断工具之命令行篇_第14张图片

  • jinfo -flags PID:查看曾经赋过值的一些参数JVM监控及诊断工具之命令行篇_第15张图片

  • jinfo -flag 具体参数 PID:查看某个Java进程的具体参数的值
    JVM监控及诊断工具之命令行篇_第16张图片

修改

可以查看被标记为manageable的参数
Linux系统下的指令
java -XX:+PrintFlagsFinal -version | grep manageable

  • 针对boolean类型的 jinfo -flag [±]具体参数 PIDJVM监控及诊断工具之命令行篇_第17张图片

  • 针对非boolean类型的 jinfo -flag 具体参数=具体参数值 PID
    JVM监控及诊断工具之命令行篇_第18张图片

拓展

  • java:-XX:+PrintFlagsInital :查看JVM启动时的初始值
  • java:-XX:+PrintFlagsFinal:查看JVM参数的最终值
  • java:-XX:+PrintCommandLineFlags:查看哪些已经被用户或者JVM设置过的详细参数XX的名称和值

jmap:到处内存映像文件和内存使用情况

基本情况

jmap(JVM Memory Map):作用一方面是获取dump文件(堆转储快照文件,二进制文件),它还可以获取目标Java进程的内存相关信息,包括Java堆各区域的使用情况、堆中对象的统计信息、类加载信息等。

开发人员可以在控制台中输入命令“jmap -help”查阅jmap工具的具体使用方式和一些标准选项配置。

官方帮助文档点这里

基本语法

它的基本使用语法为:

  • jmap [option] < pid >
  • jmap [option]
  • jmap [option] [ server_id@ ]< remote server IP or hostname>

其中option包括:

选项 作用
-dump 生成Java堆转储快照:dump文件,特别的: -dump:live只保存堆中的存活对象
-heap 输出整个堆空间的详细信息,包括GC的使用、堆配置信息,以及内存的使用信息等
-histo 输出堆空间中对象的统计信息,包括类、实例数量和合计容量 ,特别的: -histo:live只统计堆中的存活对象
-permstat 以ClassLoader为统计口径输出永久代的内存状态信息,仅linux/solaris平台有效
-finalizerinfo 显示在F-Queue中等待Finalizer线程执行finalize方法的对象,仅linux/solaris平台有效
-F 当虚拟机进程对-dump选项没有任何响应时,强制执行生成dump文件,仅linux/solaris平台有效
-h / -help jmap工具使用的帮助命令
-J< flag > 传递参数给jmap启动的jum

说明:这些参数和linux下输入显示的命令多少会有不同,包括也受jdk版本的影响。

导出内存映像文件(主要作用)

一般来说,使用jmap指令生成dump文件的操作算得上是最常用的jmap命令之一,将堆中所有存活对象导出至一个文件之中。

Heap Dump又叫做堆存储文件,指一个Java进程在某个时间点的内存快照。Heap Dump在触发内存快照的时候会保存此刻的信息如下:

  • All 0bjects:Class,fields ,primitive values and references
  • All Classes:ClassLoader ,name , super class ,static fields.
  • Garbage collection Roots:Objects defined to be reachable by the JVM.
  • Thread Stacks and Local Variables:The call-stacks of threads at the moment of the snapshot,and per-frameinformation about local objects

说明:

  1. 通常在写Heap Dump文件前会触发一次Full Gc,所以 heap dump 文件里保存的都是FullGC后留下的对象信息。
  2. 由于生成dump文件比较耗时,因此大家需要耐心等待,尤其是大内存镜像生成dump文件则需要耗费更长的时间来完成。

手动的方式

测试代码:

package priv.user.jvm;

import java.util.ArrayList;

/**
 * Description:
 * Author:江洋大盗
 * Date:2021/3/15 21:53
 * -Xms60m -Xmx60m -XX:SurvivorRatio=8
 */
public class JMAPTest {
    public static void main(String[] args) {
        ArrayList<byte[]> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            byte[] bytes = new byte[1024*100];//100kb
            list.add(bytes);
            try {
                Thread.sleep(120);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

jmap -dump:format=b,file=< filename.hprof >< pid >

这种方式会记录堆中所有的对象
JVM监控及诊断工具之命令行篇_第19张图片
在这里插入图片描述

jmap -dump:live,format=b,file=< filename.hprof > < pid >

这种方式会记录堆中所有的存活对象
在这里插入图片描述
在这里插入图片描述

自动的方式

当程序发生OOM退出系统时,一些瞬时信息都随着程序的终止而消失,而重现oOM问题往往比较困难或者耗时。此时若能在OOM时,自动导出dump文件就显得非常迫切。

这里介绍一种比较常用的取得堆快照文件的方法,即使用:
-XX:+HeapDumpOnOutOfMemoryError:在程序发生OOM时,导出应用程序的当前堆快照。
-XX:HeapDumpPath:可以指定堆快照的保存位置。

比如:
-Xmx10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\m.hprof

JVM监控及诊断工具之命令行篇_第20张图片

显示堆内存相关信息

jmap -heap pid

查看堆中当前时刻的内存使用情况

jmap -histo pid

分析堆中的对象内容

其他作用

jmap -permstat pid

查看系统的ClassLoader信息

jmap -finalizerinfo

查看堆积在finalizer队列中的对象

小结

由于jmap将访问堆中的所有对象,为了保证在此过程中不被应用线程干扰,jmap需要借助安全点机制,让所有线程停留在不改变堆中数据的状态。也就是说,由jmap导出的堆快照必定是安全点位置的。这可能导致基于该堆快照的分析结果存在偏差。

举个例子,假设在编译生成的机器码中,某些对象的生命周期在两个安全点之间,那么: live选项将无法探知到这些对象。

另外,如果某个线程长时间无法跑到安全点,jmap将一直等下去。
与前面讲的jstat则不同,垃圾回收器会主动将jstat所需要的摘要数据保存至固定位置之中,而jstat只需直接读取即可。

jhat:JDK自带的堆分析工具

基本情况

jhat(JVM Heap Analysis Tool):

Sun JDK提供的jhat命令与jmap命令搭配使用,用于分析jmap生成的heap dump文件(堆转储快照)。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,用户可以在浏览器中查看分析结果(分析虚拟机转储快照信息)。

使用了jhat命令,就启动了一个http服务,端口是7000,即http:/ /localhost:7000/,就可以在浏览器里分析。

说明:jhat命令在JDK9、JDK10中已经被删除,官方建议用VisualVM代替。
JVM监控及诊断工具之命令行篇_第21张图片
JVM监控及诊断工具之命令行篇_第22张图片

基本语法

它的基本使用语法为:
jhat [option] [ dumpfile ]

option参数:

参数 作用
option参数:-stack false/true 关闭
option参数: -refs false/true 关闭
option参数: -port port-number Server的端口号,默认7000
option参数: -exclude exclude-file 执行对象查询时需要排除的数据成员
option参数: -baseline exclude-file 指定一个基准堆转储
option参数: -debug int 设置debug级别
option参数: -version 启动后显示版本信息就退出
option参数: -J< flag > 传入启动参数,比如-J-Xmx512m

jstack:打印JVM中线程快照

基本情况

jstack(JVM Stack Trace):用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。

生成线程快照的作用:可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题。这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用jstack显示各个线程调用的堆栈情况。

官方帮助文档:

在thread dump中,要留意下面几种状态:

  • 死锁,Deadlock(重点关注)
  • 等待资源,waiting on condition(重点关注)
  • 等待获取监视器,Waiting on monitor entry(重点关注)
  • 阻塞,Blocked(重点关注)
  • 执行中,Runnable
  • 暂停,Suspended
  • 对象等待中,0bject.wait()或 TIMED_WAITING
  • 停止,Parked

死锁测试代码

package priv.user.jvm;

/**
 * Description:
 * Author:江洋大盗
 * Date:2021/3/16 10:02
 */
public class ThreadDeadLock {
    public static void main(String[] args) {
        StringBuilder s1 = new StringBuilder();
        StringBuilder s2 = new StringBuilder();

        new Thread(()->{
            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(()->{
            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();
    }
}

JVM监控及诊断工具之命令行篇_第23张图片
线程等待测试代码

package priv.user.jvm;

/**
 * Description:
 * Author:江洋大盗
 * Date:2021/3/16 10:20
 */
public class ThreadSleep {
    public static void main(String[] args) {
        System.out.println("hello world");
        try {
            Thread.sleep(1000 * 60 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
线程同步代码

package priv.user.jvm;

/**
 * Description:
 * Author:江洋大盗
 * Date:2021/3/16 10:24
 */
public class ThreadSynTest {
    public static void main(String[] args) {
        GetNum num = new GetNum();
        Thread t1 = new Thread(num);
        Thread t2 = new Thread(num);

        t1.setName("线程1");
        t2.setName("线程2");

        t1.start();
        t2.start();
    }
}

class GetNum implements Runnable {
    private int num = 1;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (num <= 100) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + num);
                    num++;
                } else {
                    break;
                }
            }
        }
    }
}

JVM监控及诊断工具之命令行篇_第24张图片

基本用法

option参数

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

补充:
Java的Thread类中有一个方法可以输出当前的线程信息,不过效果不如jstack明显:

package priv.user.jvm;

import java.util.Map;

/**
 * Description:
 * Author:江洋大盗
 * Date:2021/3/16 10:46
 */
public class AllThreadTrace {
    public static void main(String[] args) {
        Map<Thread, StackTraceElement[]> all = Thread.getAllStackTraces();
        for (Map.Entry<Thread, StackTraceElement[]> entry : all.entrySet()) {
            Thread t = entry.getKey();
            StackTraceElement[] v = entry.getValue();

            System.out.println("【Thread name:" + t.getName() + "】");
            for (StackTraceElement s : v) {
                System.out.println("\t" + s.toString());
            }
        }
    }
}

jcmd:多功能的命令行

基本情况

在JDK 1.7以后,新增了一个命令行工具jcmd。

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

官方帮助文档

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

基本用法

  • jcmd -l :列出所有的JVM进程
  • jcmd pid help:针对指定的进程,列出支持的所有命令
  • jcmd pid 具体命令:显示指定进程的指令命令的数据

JVM监控及诊断工具之命令行篇_第25张图片

JVM监控及诊断工具之命令行篇_第26张图片

jstatd:远程主机信息收集

之前的指令只涉及到监控本机的Java应用程序,而在这些工具中,一些监控工具也支持对远程计算机的监控(如jps、jstat)。为了启用远程监控,则需要配合使用jstatd 工具。

命令jstatd是一个RMI服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd服务器将本机的Java应用程序信息传递到远程计算机。

JVM监控及诊断工具之命令行篇_第27张图片

结语

除了奋斗,我们别无选择

你可能感兴趣的:(jvm学习之旅)