JVM系列之:你知道Jhsdb整合的故障处理工具

简介: JVM系列之:你知道Jhsdb整合的故障处理工具

1.jpg

本文为《深入学习 JVM 系列》第二十一篇文章


Jhsdb 是 JDK9 引入的新的命令行工具,它有 clhsdb、debugd、hsdb、jstack、jmap、jinfo、jsnap 这些 mode 可以使用,其中有几个在名称和功能上与以前的 JDK 发行版中可用的各个命令行工具相对应。看得出来,官方想要 jhsdb 工具整合多个其他工具的功能,甚至还做了一些功能拓展。所以本文将带大家认识一下 jhsdb 下的这些 mode。


在使用 jhsdb 工具之前,必须先获取 PID,所以我们先来认识一下 jps 命令。


jps


jps(JVM Process Status Tool)可以列出正在运行的虚拟机进程,需要注意的是,jps 仅查找当前用户的 Java 进程,而不是当前系统中的所有进程。


jps [options] [hostid]
复制代码

在默认情况下,jps 的输出信息包括 Java 进程的进程 ID 以及主类名。我们还可以通过追加参数,来打印额外的信息。


  • -v:输出传递给 JVM 的参数(如-XX:+UnlockExperimentalVMOptions -XX:+UseZGC)
  • -l: 打印模块名以及包名
  • -m:将打印传递给主类的参数


或者根据进程名称查找准确的 id。


ps aux|grep xxxx
复制代码


需要注意的是,如果某 Java 进程关闭了默认开启的UsePerfData参数(即使用参数-XX:-UsePerfData),那么 jps 命令就无法探知该 Java 进程。


当获得 Java 进程的进程 ID 之后,我们便可以调用接下来介绍的各项监控及诊断工具了。


除了任何必需 jstackjmapjinfojsnap 特定于模式的选项外,pidexecore 这三个选项适用于所有模式。


  • --pid
    挂起进程的进程ID。
  • --exe
    可执行文件名。
  • --core
    核心转储文件名。


关于 --exe 和 --core 的使用,暂时没有测试成功。


jmap


jmap(Memory Map for Java)命令用于生成堆转储快照(一般称为heapdump或dump文件)。如果不想使用 jmap 命令,可以配置程序启动时的 JVM 参数,比如说 -XX:+HeapDumpOnOutOfMemoryError 参数,可以在程序发生内存溢出异常后自动生成 dump 文件。通过 -XX:+HeapDumpOnCtrlBreak 参数则可以使用[Ctrl]+[Break]键让虚拟机生成堆转储快照文件,又或者在 Linux 系统下通过 Kill-3 命令发送进程退出信号“恐吓”一下虚拟机,也能顺利拿到堆转储快照。

JDK 自带的 jmap 命令格式如下:


jmap [option] [pid]
jmap -clstats <pid>   to connect to running process and print class loader statistics
jmap -finalizerinfo <pid> to connect to running process and print information on objects awaiting finalization
jmap -histo[:live] <pid>
  to connect to running process and print histogram of java object heap
  if the "live" suboption is specified, only count live objects
jmap -dump:<dump-options> <pid> to connect to running process and dump java heap
  dump-options:
    live         dump only live objects; if not specified,
    all objects in the heap are dumped.
    format=b     binary format
    file=<file>  dump heap to <file>
复制代码


可以看到 jmap 已经没有了-heap 命令参数,我们来看一下 jhsdb jmap,其中 options 位置不一定非要在后面。


jhsdb jmap [--pid pid | --exe executable --core coredump] [options]
// //其中 options 包括:
<no option> to print same info as Solaris pmap
--heap  to print java heap summary    //显示Java堆详细信息
--binaryheap  to dump java heap in hprof binary format
--dumpfile  name of the dump file //导出 Java 虚拟机堆的快照,生成文件
--histo to print histogram of java object heap  //打印 Java 对象堆的直方图
--clstats to print class loader statistics  //打印 Java 堆的类加载器统计信息
--finalizerinfo to print information on objects awaiting finalization //打印有关等待完成的对象的信息
复制代码


(不能再使用 jmap -heap pid 的命令了,需要使用上面的命令)。使用旧的命令会报错:


Error: -heap option used
Cannot connect to core dump or remote debug server. Use jhsdb jmap instead
复制代码


这里只演示两个简单示例,jmap -heap pid 展示 pid 的整体堆信息


//JDK9 
% jhsdb jmap --pid 2681 --heap
Attaching to process ID 2681, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 9.0.4+11
using thread-local object allocation.
Mark Sweep Compact GC
Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 4294967296 (4096.0MB)
   NewSize                  = 10485760 (10.0MB)
   MaxNewSize               = 10485760 (10.0MB)
   OldSize                  = 257949696 (246.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 9437184 (9.0MB)
   used     = 1718600 (1.6389846801757812MB)
   free     = 7718584 (7.361015319824219MB)
   18.210940890842014% used
Eden Space:
   capacity = 8388608 (8.0MB)
   used     = 1718600 (1.6389846801757812MB)
   free     = 6670008 (6.361015319824219MB)
   20.487308502197266% used
From Space:
   capacity = 1048576 (1.0MB)
   used     = 0 (0.0MB)
   free     = 1048576 (1.0MB)
   0.0% used
To Space:
   capacity = 1048576 (1.0MB)
   used     = 0 (0.0MB)
   free     = 1048576 (1.0MB)
   0.0% used
tenured generation:
   capacity = 257949696 (246.0MB)
   used     = 0 (0.0MB)
   free     = 257949696 (246.0MB)
   0.0% used
2851 interned Strings occupying 192840 bytes.
复制代码


这一命令相较于我们在 clhsdb 中执行的 universe 命令更加详细,可以看到详细的内存分配。


jmap -histo pid 展示 class 的内存情况


说明:instances(实例数)、bytes(大小)、classs name(类名)。它基本是按照使用使用大小逆序排列的。


% jhsdb jmap --pid 2681 --histo
Attaching to process ID 2681, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 9.0.4+11
Iterating over heap. This may take a while...
Object Histogram:
num     #instances  #bytes  Class description
--------------------------------------------------------------------------
1:    3885  205144  byte[]
2:    3497  111904  java.util.HashMap$Node
3:    1038  91248 java.lang.Object[]
4:    3393  81432 java.lang.String
5:    687 74920 java.util.HashMap$Node[]
6:    601 72984 java.lang.Class
7:    1376  44032 java.util.concurrent.ConcurrentHashMap$Node
8:    780 37440 java.util.HashMap
9:    24  35968 char[]
//只输出前30的对象
% jhsdb jmap --histo --pid 2681 | head -n30
复制代码


关于该 jmap 的更多讲解,推荐阅读:java命令--jmap命令使用

关于 jmap 命令,使用最多的场景就是生成 dump 文件,其他命令的效果可有后续介绍到的监控工具来代替,比如说 VisualVM 等。下面是不同 JDK 环境导出 dump 文件的命令,不过需要注意的是,通过命令行方式执行时,JVM 是暂停服务的,所以对线上的运行会产生影响。不推荐该方式。


jhsdb jmap 命令也可以生成 dump 文件,下面两个命令都是可以的。


//JDK9
jhsdb jmap --binaryheap --dumpfile heap.hprof --pid 17714
jmap -dump:format=b,file=heap.hprof 17594
复制代码


在测试 JDK9 导出 dump 文件时遇到了个小问题,首先我是在 IDEA 中启动该程序,然后在命令行端口获取 pid,然后执行 jhsdb jmap 命令,结果报错了。


% jhsdb jmap --binaryheap --dumpfile heap.hprof --pid 17407
Attaching to process ID 17407, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 9.0.4+11
Exception in thread "main" sun.jvm.hotspot.types.WrongTypeException: No suitable match for type of address 0x000000032b938308
  at jdk.hotspot.agent/sun.jvm.hotspot.runtime.InstanceConstructor.newWrongTypeException(InstanceConstructor.java:62)
  at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VirtualBaseConstructor.instantiateWrapperFor(VirtualBaseConstructor.java:109)
  at jdk.hotspot.agent/sun.jvm.hotspot.oops.Metadata.instantiateWrapperFor(Metadata.java:73)
  at jdk.hotspot.agent/sun.jvm.hotspot.oops.Oop.getKlassForOopHandle(Oop.java:211)
  at jdk.hotspot.agent/sun.jvm.hotspot.oops.ObjectHeap.newOop(ObjectHeap.java:252)
  at jdk.hotspot.agent/sun.jvm.hotspot.oops.ObjectHeap.iterateLiveRegions(ObjectHeap.java:331)
  at jdk.hotspot.agent/sun.jvm.hotspot.oops.ObjectHeap.iterate(ObjectHeap.java:172)
  at jdk.hotspot.agent/sun.jvm.hotspot.utilities.AbstractHeapGraphWriter.write(AbstractHeapGraphWriter.java:51)
  at jdk.hotspot.agent/sun.jvm.hotspot.utilities.HeapHprofBinWriter.write(HeapHprofBinWriter.java:433)
  at jdk.hotspot.agent/sun.jvm.hotspot.tools.JMap.writeHeapHprofBin(JMap.java:182)
  at jdk.hotspot.agent/sun.jvm.hotspot.tools.JMap.run(JMap.java:97)
  at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.startInternal(Tool.java:260)
  at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.start(Tool.java:223)
  at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.execute(Tool.java:118)
  at jdk.hotspot.agent/sun.jvm.hotspot.tools.JMap.main(JMap.java:176)
  at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.runJMAP(SALauncher.java:32
复制代码


关于错误原因,可以参考一下这篇文章,虽然错误不是完全一致,但是错误原因可以借鉴一下,初步认为是 IDEA 中程序运行时和命令行窗口中执行 jhsdb jmap 命令时,JVM 环境不一致。抱着试一试的态度,我在命令行窗口启动该程序,然后再次执行 jhsdb jmap 命令,成功生成 dump 文件。


% jhsdb jmap --binaryheap --dumpfile heap.hprof --pid 17714
Attaching to process ID 17714, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 9.0.4+11
heap written to heap.hprof
复制代码


jinfo


jinfo(Configuration Info for Java)的作用是实时查看和调整虚拟机各项参数。使用 jps 命令的-v参数可以查看虚拟机启动时显式指定的参数列表,但如果想知道未被显式指定的参数的系统默认值,除了去找资料外,就只能使用 jinfo 的-flag 选项进行查询了。


Usage:
    jinfo <option> <pid>
       (to connect to a running process)
where <option> is one of:
    -flag <name>         to print the value of the named VM flag
    -flag [+|-]<name>    to enable or disable the named VM flag
    -flag <name>=<value> to set the named VM flag to the given value
    -flags               to print VM flags
    -sysprops            to print Java system properties
    <no option>          to print both VM flags and system properties
    -h | -help           to print this help message
复制代码


比如可以使用 jinfo -flag +HeapDumpAfterFullGC 命令,开启所指定的 Java 进程的 HeapDumpAfterFullGC 参数。


再来看看 jhsdb jinfo 命令格式如下:


jhsdb jinfo [--pid pid | --exe executable --core coredump] [options]
//其中 options 包括:                   
    --flags to print VM flags //打印 VM 标志。
    --sysprops  to print Java System properties //打印 Java 系统属性
    <no option> to print both of the above  //打印 VM 标志和 Java 系统属性
复制代码

而 --flags 只能输出进程配置的 JVM 参数


% jhsdb jinfo --pid 17714 --flags
Attaching to process ID 17714, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 9.0.4+11
Non-default VM flags: -XX:CICompilerCount=4 -XX:ConcGCThreads=3 -XX:G1ConcRefinementThreads=10 -XX:G1HeapRegionSize=1048576 -XX:InitialHeapSize=268435456 -XX:MarkStackSize=4194304 -XX:MaxHeapSize=4294967296 -XX:MaxNewSize=2576351232 -XX:MinHeapDeltaBytes=1048576 -XX:NonNMethodCodeHeapSize=5835340 -XX:NonProfiledCodeHeapSize=122911450 -XX:ProfiledCodeHeapSize=122911450 -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:-UseAOT -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseG1GC 
Command line:
复制代码


jinfo --pid 用来查看目标 Java 进程的参数。


具体示例如下:


% jhsdb jinfo --pid 39050
复制代码

2.jpg


jsnap


jhsdb jsnap 打印性能计数器信息,格式如下


jhsdb jsnap [options] [--pid pid | --exe executable --core coredump] 
//其中 options 包括:
    --all to print all performance counters
复制代码


jsnap --pid 打印性能计数器信息。


% jhsdb jsnap --pid 3563
Attaching to process ID 3563, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 9.0.4+11
java.threads.started=5 event(s)
java.threads.live=5
java.threads.livePeak=5
java.threads.daemon=4
java.cls.loadedClasses=519 event(s)
java.cls.unloadedClasses=0 event(s)
java.cls.sharedLoadedClasses=0 event(s)
java.cls.sharedUnloadedClasses=0 event(s)
java.ci.totalTime=26320291 tick(s)
java.property.java.vm.specification.version=9
java.property.java.vm.specification.name=Java Virtual Machine Specification
java.property.java.vm.specification.vendor=Oracle Corporation
java.property.java.vm.version=9.0.4+11
java.property.java.vm.name=Java HotSpot(TM) 64-Bit Server VM
java.property.java.vm.vendor=Oracle Corporation
java.property.java.vm.info=mixed mode
java.property.jdk.debug=release
java.property.java.library.path=......
java.property.java.version=9.0.4
java.property.java.home=/Library/Java/JavaVirtualMachines/jdk-9.0.4.jdk/Contents/Home
java.rt.vmFlags=
java.rt.vmArgs=-XX:+UseSerialGC -Xmn10M -Xdebug -Xrunjdwp:transport=dt_socket,address=Ankangs-MacBook-Pro.local:55217,suspend=y
复制代码


jstack


jstack 打印目标 Java 进程中各个线程的栈轨迹,以及这些线程所持有的锁。


jhsdb jstack [--pid pid | --exe executable --core coredump] [options]
// options包括:
    --locks to print java.util.concurrent locks
    --mixed to print both java and native frames (mixed mode)
复制代码


比如有这样一段死锁代码:


public class SyncDeadLock {
  private static Object objectA = new Object();
  private static Object objectB = new Object();
  public static void main(String[] args) {
    new SyncDeadLock().deadLock();
  }
  private void deadLock() {
    Thread thread1 = new Thread(new Runnable() {
      @Override
      public void run() {
        synchronized (objectA) {
          try {
            System.out.println(Thread.currentThread().getName() + " get objectA ing!");
            Thread.sleep(500);
          } catch (Exception e) {
            e.printStackTrace();
          }
          System.out.println(Thread.currentThread().getName() + " need objectB! Just waiting!");
          synchronized (objectB) {
            System.out.println(Thread.currentThread().getName() + " get objectB ing!");
          }
        }
      }
    }, "thread1");
    Thread thread2 = new Thread(() -> {
      synchronized (objectB) {
        try {
          System.out.println(Thread.currentThread().getName() + " get objectB ing!");
          Thread.sleep(500);
        } catch (Exception e) {
          e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " need objectA! Just waiting!");
        synchronized (objectA) {
          System.out.println(Thread.currentThread().getName() + " get objectA ing!");
        }
      }
    }, "thread2");
    thread1.start();
    thread2.start();
  }
}
复制代码


执行上述代码,输出结果如下:


thread1 get objectA ing!
thread2 get objectB ing!
thread1 need objectB! Just waiting!
thread2 need objectA! Just waiting!
复制代码


可以看出两个线程各拥有一个对象的锁,未释放锁之前,又想获取对方拥有的对象的锁,最终导致死锁。


接下来我们执行 jstack 命令来看一看死锁情况:


% jhsdb jstack --locks --pid 39186
Attaching to process ID 39186, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 9.0.4+11
Deadlock Detection:
Found one Java-level deadlock:
=============================
"thread1":
  waiting to lock Monitor@0x00007faf2b313f00 (Object@0x00000006cf535808, a java/lang/Object),
  which is held by "thread2"
"thread2":
  waiting to lock Monitor@0x00007faf2b313d00 (Object@0x00000006cf5357f8, a java/lang/Object),
  which is held by "thread1"
Found a total of 1 deadlock.
Thread 27907: (state = BLOCKED)
Locked ownable synchronizers:
    - None
Thread 3587: (state = BLOCKED)
Locked ownable synchronizers:
    - None
Thread 36867: (state = BLOCKED)
 - com.msdn.java.commandLine.SyncDeadLock.lambda$deadLock$0() @bci=86, line=46 (Interpreted frame)
 - com.msdn.java.commandLine.SyncDeadLock$$Lambda$1.run() @bci=0 (Interpreted frame)
 - java.lang.Thread.run() @bci=11, line=844 (Interpreted frame)
Locked ownable synchronizers:
    - None
Thread 37123: (state = BLOCKED)
 - com.msdn.java.commandLine.SyncDeadLock$1.run() @bci=86, line=30 (Interpreted frame)
 - java.lang.Thread.run() @bci=11, line=844 (Interpreted frame)
Locked ownable synchronizers:
    - None
Thread 37891: (state = IN_NATIVE)
 - java.net.SocketInputStream.socketRead0(java.io.FileDescriptor, byte[], int, int, int) @bci=0 (Interpreted frame)
 - java.net.SocketInputStream.socketRead(java.io.FileDescriptor, byte[], int, int, int) @bci=8, line=116 (Interpreted frame)
 - java.net.SocketInputStream.read(byte[], int, int, int) @bci=117, line=171 (Interpreted frame)
 - java.net.SocketInputStream.read(byte[], int, int) @bci=11, line=141 (Interpreted frame)
 - sun.nio.cs.StreamDecoder.readBytes() @bci=135, line=284 (Interpreted frame)
 - sun.nio.cs.StreamDecoder.implRead(char[], int, int) @bci=112, line=326 (Interpreted frame)
 - sun.nio.cs.StreamDecoder.read(char[], int, int) @bci=180, line=178 (Interpreted frame)
 - java.io.InputStreamReader.read(char[], int, int) @bci=7, line=185 (Interpreted frame)
 - java.io.BufferedReader.fill() @bci=145, line=161 (Interpreted frame)
 - java.io.BufferedReader.readLine(boolean) @bci=44, line=326 (Interpreted frame)
 - java.io.BufferedReader.readLine() @bci=2, line=392 (Interpreted frame)
 - com.intellij.rt.execution.application.AppMainV2$1.run() @bci=36, line=61 (Interpreted frame)
Locked ownable synchronizers:
    - None
Thread 38403: (state = BLOCKED)
 - java.lang.Object.wait(long) @bci=0 (Interpreted frame)
 - java.lang.ref.ReferenceQueue.remove(long) @bci=59, line=151 (Interpreted frame)
 - jdk.internal.ref.CleanerImpl.run() @bci=65, line=148 (Interpreted frame)
 - java.lang.Thread.run() @bci=11, line=844 (Interpreted frame)
 - jdk.internal.misc.InnocuousThread.run() @bci=20, line=122 (Interpreted frame)
Locked ownable synchronizers:
    - None
Thread 40195: (state = BLOCKED)
Locked ownable synchronizers:
    - None
Thread 23299: (state = BLOCKED)
 - java.lang.Object.wait(long) @bci=0 (Interpreted frame)
 - java.lang.ref.ReferenceQueue.remove(long) @bci=59, line=151 (Interpreted frame)
 - java.lang.ref.ReferenceQueue.remove() @bci=2, line=172 (Interpreted frame)
 - java.lang.ref.Finalizer$FinalizerThread.run() @bci=37, line=216 (Interpreted frame)
Locked ownable synchronizers:
    - None
Thread 42243: (state = BLOCKED)
 - java.lang.ref.Reference.waitForReferencePendingList() @bci=0 (Interpreted frame)
 - java.lang.ref.Reference.processPendingReferences() @bci=0, line=174 (Interpreted frame)
 - java.lang.ref.Reference.access$000() @bci=0, line=44 (Interpreted frame)
 - java.lang.ref.Reference$ReferenceHandler.run() @bci=0, line=138 (Interpreted frame)
Locked ownable synchronizers:
    - None
复制代码


我们可以看到,jstack不仅会打印线程的栈轨迹、线程状态(BLOCKED)、持有的锁(locked …)以及正在请求的锁(waiting to lock …),而且还会分析出具体的死锁。

目录
相关文章
|
3月前
|
缓存 Java 中间件
jvm性能调优实战 -55RPC调用引发的OOM故障
jvm性能调优实战 -55RPC调用引发的OOM故障
56 0
|
5月前
|
监控 数据可视化 Java
深入理解JVM系列教程(09) - JDK可视化工具
深入理解JVM系列教程(09) - JDK可视化工具
27 1
|
5月前
|
监控 数据可视化 Java
visualvm工具远程对linux服务器上的JVM虚拟机进行监控与调优
本文档主要总结在window本地环境远程对linux服务断的JVM虚拟机进行监控与调优的方法。
74 0
|
17天前
|
缓存 Java C#
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(一)
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍
57 0
|
7月前
|
监控 Oracle 数据可视化
深度解析JVM性能监控工具:推荐与详细用法
深度解析JVM性能监控工具:推荐与详细用法
206 0
|
2月前
|
监控 算法 NoSQL
深入理解JVM - 实战JVM工具(上)
深入理解JVM - 实战JVM工具(上)
69 0
|
4月前
|
数据可视化 Java
jvm 调优工具
jvm 调优工具
30 0
|
4月前
|
Java Linux C++
JVM调优工具之jstack
JVM调优工具之jstack
33 0
|
5月前
|
监控 Java
JVM日志分析及工具
JVM日志分析及工具
48 0
|
6月前
|
监控 Oracle Java
Java 最常见的面试题:说一下 jvm 调优的工具?
Java 最常见的面试题:说一下 jvm 调优的工具?