深入理解JVM(十九)一一 JVM监控及诊断工具(命令行)

765 阅读14分钟

JVM监控及诊断工具(命令行)

jdk命令行

查看jdk相关命令源码

jps:查看正在运行java进程

查看正在运行的java进程

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

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

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

hostid为RMI注册表中注册的主机名。

如果想要远程监控主机上的java程序,需要安装jstatd。 对于具有更严格的安全实践的网络场所而言,可能使用一个自定义的策略文件来显示对特定的可信主机或网络的访问,尽管这种技术容易受到IP地址欺诈攻击。

如果安全问题无法使用一个定制的策略文件来处理,那么最安全的操作是不运行jstatd服务器,而是在本地使用jstat和jps工具。

image.png

image.png

image.png

jstat:查看JVM统计信息

jstat(VM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。

官方文档

image.png

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

  • 类装载相关的:

    • -class:显示ClassLoader的相关信息:类的装载、卸载数量、总空间、类装载所消耗的时间等
      • -t :前面显示Timestamp一列
      • -h3 :每隔三次输出标题
      • 9000 :进程号
      • 1000 :每隔1000毫米输出一次
      • 10 :一共输出10次 image.png
  • 垃圾回收相关的:

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

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

image.png

  • 本身命令相关:
    • -interval:用于指定输出统计数据的周期,单位为毫秒。即:查询间隔
    • -count:用于指定查询的总次数
    • -t:可以在输出信息前加上一个Timestamp列,显示程序的运行时间。单位:秒
    • -h:可以在周期性数据输出时,输出多少行数据后输出一个表头信息

-gc参数详解输出项

image.png

  • 新生代相关
    • S0C是第一个幸存者区的大小(字节)
    • S1C是第二个幸存者区的大小(字节)
    • S0U是第一个幸存者区已使用的大小(字节)
    • S1U是第二个幸存者区已使用的大小(字节)
    • EC是Eden空间的大小(字节)
    • EU是Eden空间已使用大小(字节)
  • 老年代相关
    • OC是老年代的大小(字节)
    • OU是老年代已使用的大小((字节)
  • 方法区(元空间)相关
    • MC是方法区的大小
    • MU是方法区已使用的大小
    • CCSC是压缩类空间的大小
    • CCSU是压缩类空间已使用的大小
  • 其它
    • YGC是指从应用程序启动到采样时young gc次数
    • YGCT是指从应用程序启动到采样时young gc消耗的时间(秒)
    • FGC是指从应用程序启动到采样时full gc次数
    • FGCT是指从应用程序启动到采样时full gc消耗的时间(秒)
    • GCT是指从应用程序启动到采样时gc的总时间
  1. jstat命令-OOM预警判断

我们可以比较Java进程的启动时间以及总GC时间(GCT列),或者两次测量的间隔时间以及总GC时间的增量,来得出 GC时间占运行时间的比例。 如果该比例超过20%,则说明目前堆的压力较大;如果该比例超过90%,则说明堆里几乎没有可用空间,随时都可能抛出OOM异常。

GCT时间/(下次Timestamp-上次Timestamp)

  1. jstat判断是否出现内存泄漏
  • 第1步: 在长时间运行的Java程序中,我们可以运行jstat命令连续获取多行性能数据,并取这几行数据中OU列(即已占用的老年代内存)的最小值。
  • 第2步: 然后,我们每隔一段较长的时间重复一次上述操作,来获得多组ou最小值。如果这些值呈上涨趋势,则说明该Java程序的老年代内存己使用量在不断上涨,这意味着无法回收的对象在不断增加,因此很有可能存在内存泄漏。

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

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

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

基本语法为: jinfo [options] pid

image.png

jinfo -sysprops PID:可以查看由System.getProperties()取得的参数

实时查看

  • jinfo -flags PID :查看曾经赋过值的一些参数
  • jinfo -flag 具体参数 PID:查看某个java进程的具体参数的值

image.png

image.png

实时修改

jinfo不仅可以查看运行时某一个Java虚拟机参数的实际取值,甚至可以在运行时修改部分参数,并使之立即生效。

但是,并非所有参数都支持动态修改。参数只有被标记为manageable的flag可以被实时修改。其实,这个修改能力是极其有限的。

可以查看被标记为manageable的参数:java -XX:+PrintFlagsFinal -version | grep manageable

image.png

  • 针对boolean类型:jinfo -flag [+/-]具体参数 PID
  • 针对非boolean类型:jinfo -flag 具体参数=具体参数值 PID

image.png

  • java -XX:+PrintFlagsInitial:查看所有JVM参数启动的初始值
  • java -XX:+PrintFlagsFinal: 查看所有JVM参数的最终值,有冒号的值为修改过的
  • java -XX:+PrintCommandLineFlags:查看那些已经被用户或者JVM设置过的详细的XX参数的名称和值

jmap:导出内存映像文件&内存使用情况

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

基本语法为:

  1. jmap [option] <pid>
  2. jmap [option] <executable <core>
  3. jmap [option] [server_id@] <remote server IP or hostname>
  • -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启动的jvm

导出堆转储快照文件

将堆中所有存活对象导出至一个文件之中。Heap Dump又叫做堆存储文件,指一个Java进程在某个时间点的内存快照。

通常在写Heap Dump文件前会触发一次Full Gc,所以heap dump文件里保存的都是Ful1GC后留下的对象信息。

由于生成dump文件比较耗时,尤其是大内存镜像生成dump文件则需要耗费更长的时间来完成。

  • 手动的方式
    • jmap -dump:format=b,file=<filename.hprof> <pid> (不荐导全部导出)
    • jmap -dump:live,format=b,file=<filename.hprof> <pid> (生产推荐导出活的对象)

image.png

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

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

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

  • 自动的方式
    • -XX:+HeapDumpOnoutOfMemoryError (在程序发生OOM时,导出应用程序的当前堆快照)
    • -XX:HeapDumpPath=<filename.hprof> (可以指定堆快照的保存位置)

当程序发生OOM退出系统时,一些瞬时信息都随着程序的终止而消失,而重现OOM问题往往比较困难或者耗时。此时若能在OOM时,自动导出dump文件就显得非常迫切。 比如:-Xmx100m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\m.hprof

显示堆内存相关信息

jmap -heap pid :显示内存各个区的使用情况

image.png

jmap -histo pid : 排序查看堆中各种对象大小

image.png

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代替。

image.png

image.png

image.png

jstack: 打印JVM线程快照

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

官方帮助文档

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

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

option参数:

  • -F:当正常输出的请求不被响应时,强制输出线程堆栈
  • -m:除堆栈外,显示关于锁的附加信息
  • -h:如果调用到本地方法的话,可以显示C/C++的堆栈
=/**
 * @Description: 死锁例子: jstack查看死锁
 * @Author: jianweil
 * @date: 2021/9/6 16:07
 */
=public class DealLockTest {

    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (o1) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程1开始运行");
                    synchronized (o2) {
                        System.out.println("线程2获取到o1");
                    }

                }
            }
        }, "线程1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (o2) {
                    System.out.println("线程2开始运行");
                    synchronized (o1) {
                        System.out.println("线程2获取到o1");
                    }

                }
            }
        }, "线程2").start();

        System.out.println("main线程结束");
    }
}

image.png

image.png

java获取栈信息

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

输出结果:

【Thread name is :Finalizer】
	java.lang.Object.wait(Native Method)
	java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
	java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
	java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
【Thread name is :Monitor Ctrl-Break】
	java.lang.ClassLoader$NativeLibrary.load(Native Method)
	java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1941)
	java.lang.ClassLoader.loadLibrary(ClassLoader.java:1845)
	java.lang.Runtime.loadLibrary0(Runtime.java:870)
	java.lang.System.loadLibrary(System.java:1122)
	java.net.InetAddress$1.run(InetAddress.java:295)
	java.net.InetAddress$1.run(InetAddress.java:293)
	java.security.AccessController.doPrivileged(Native Method)
	java.net.InetAddress.<clinit>(InetAddress.java:292)
	java.net.InetSocketAddress.<init>(InetSocketAddress.java:220)
	java.net.Socket.<init>(Socket.java:211)
	com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:44)
【Thread name is :Signal Dispatcher】
【Thread name is :Reference Handler】
	java.lang.Object.wait(Native Method)
	java.lang.Object.wait(Object.java:502)
	java.lang.ref.Reference.tryHandlePending(Reference.java:191)
	java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
【Thread name is :main】
	java.lang.Thread.dumpThreads(Native Method)
	java.lang.Thread.getAllStackTraces(Thread.java:1610)
	com.atguigu.java.DealLockTest.main(DealLockTest.java:15)
【Thread name is :Attach Listener】

jcmd :多功能命令行

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

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

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

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

image.png

image.png

image.png

jcmd pid help是把相关查询功能查询出来,相当于目录,根据这些参数(目录)查询出不同的信息

jstatd :远程主机信息收集

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

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

VisualVM监控远程主机上的JAVA应用程序

image.png

深入理解JVM系列