Jhsdb 是 JDK9 引入的新的命令行工具,它有 clhsdb、debugd、hsdb、jstack、jmap、jinfo、jsnap 这些 mode 可以使用,其中有几个在名称和功能上与以前不同 JDK 发行版中可用的各个命令行工具相对应。看得出来,官方想要 jhsdb 工具整合了其他工具的功能,甚至还做了一些功能拓展。所以本文将带大家认识一下 jhsdb 下的这些 mode。
在使用 jhsdb 工具之前,必须先获取 PID,所以我们先来认识一下 jps 命令。
jpsjps(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 之后,我们便可以调用接下来介绍的各项监控及诊断工具了。
除了任何必需 jstack、jmap、jinfo或jsnap 特定于模式的选项外,pid 、exe、core 这三个选项适用于所有模式。
- --pid
- 挂起进程的进程ID。
- --exe
- 可执行文件名。
- --core
- 核心转储文件名。
关于 --exe 和 --core 的使用,暂时没有测试成功。
jmapjmap(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(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
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 打印目标 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 …),而且还会分析出具体的死锁。
,