你知道Jhsdb整合的故障处理工具吗

hresh 435 0

你知道Jhsdb整合的故障处理工具吗

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.HashMapNode
3:      1038    91248   java.lang.Object[]
4:      3393    81432   java.lang.String
5:      687 74920   java.util.HashMapNode[]
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

你知道Jhsdb整合的故障处理工具吗

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.lambdadeadLock0() @bci=86, line=46 (Interpreted frame)
 - com.msdn.java.commandLine.SyncDeadLock$Lambda1.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.SyncDeadLock1.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.AppMainV21.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.FinalizerFinalizerThread.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.access000() @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 …),而且还会分析出具体的死锁。

参考文献

JVM 性能调优工具讲解 jstat、jmap、jstack、jinfo

Java程序员必备:jstack命令解析

《深入理解Java虚拟机》

发表评论 取消回复
表情 图片 链接 代码

分享