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 以及主类名。我们还可以通过追加参数,来打印额外的信息。

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

ps aux|grep xxxx

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

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

除了任何必需 jstack、jmap、jinfo或jsnap 特定于模式的选项外,pid 、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

常用的jvm调优策略(你知道Jhsdb整合的故障处理工具)(1)

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 …),而且还会分析出具体的死锁。

,