JVM中jhsdb调试教程

jhsdb是JDK9中一个相对未被开发但非常强大的工具,用于调试 JVM 问题。无论您是在处理导致 JVM 崩溃的本机代码,还是深入研究复杂的性能分析,了解如何jhsdb有效使用都可以成为您调试工具库中的游戏规则改变者。

什么jhsdb?
Java 9 引入了许多变化,其中模块是亮点。然而,在这些重大转变之中,jhsdb并没有得到应有的重视。 Oracle 官方将其描述jhsdb为可服务性代理工具,它是 JDK 的一部分,旨在进行快照调试、性能分析,并提供对 Hotspot JVM 和在其上运行的 Java 应用程序的深入了解。简而言之,jhsdb能深入 JVM 内部结构、了解核心转储以及诊断 JVM 或本机库故障的首选工具。

jhsdb 入门
首先我们可以调用:

$ jhsdb --help
clhsdb           command line debugger
hsdb             ui debugger
debugd --help    to get more information
jstack --help    to get more information
jmap   --help    to get more information
jinfo  --help    to get more information
jsnap  --help    to get more information


该命令显示jhsdb包含六个不同的工具:

  1. debugd:用于远程连接和诊断的远程调试服务器。
  2. jstack:提供详细的堆栈和锁信息。
  3. jmap:提供对堆内存的深入了解。
  4. jinfo:显示 JVM 基本信息。
  5. jsnap:协助性能数据。
  6. Command Line Debugger命令行调试器:尽管人们更喜欢 GUI,但我们将重点关注 GUI 调试,以获得更直观的方法。

理解和使用debugd
debugd由于其远程调试性质,可能不是您生产环境的首选。然而,它对于本地容器调试可能很有价值。
要使用它,我们首先需要检测 JVM 进程 ID (PID),我们可以使用jps命令来完成此操作。
不幸的是,由于 UI 中的错误,您当前无法通过 GUI 调试器连接到远程服务器。
我只能将其与命令行工具一起使用,例如jstack(下面讨论)。
jhsdb debugd --pid 1234

我们可以连接到进程 1234。然后我们可以使用类似的工具jstack来获取附加信息:
jhsdb jstack --connect localhost

--connect参数适用于全局并且应该适用于所有命令。

利用 jstack 进行线程转储
jstack有助于生成线程转储,这对于分析用户计算机或生产环境中的堆栈进程至关重要。该命令可以显示详细的 JVM 运行状态,包括死锁检测、线程状态和编译见解。

通常我们会jstack在本地使用,这样就不需要debugd:

$ jhsdb jstack --pid 1234
Attaching to process ID 1234, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0.13+8-LTS
Deadlock Detection:

No deadlocks found.

"Keep-Alive-Timer" #189 daemon prio=8 tid=0x000000011d81f000 nid=0x881f waiting on condition [0x0000000172442000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
   JavaThread state: _thread_blocked
 - java.lang.Thread.sleep(long) @bci=0 (Interpreted frame)
 - sun.net.www.http.KeepAliveCache.run() @bci=3, line=168 (Interpreted frame)
 - java.lang.Thread.run() @bci=11, line=829 (Interpreted frame)
 - jdk.internal.misc.InnocuousThread.run() @bci=20, line=134 (Interpreted frame)


"DestroyJavaVM" #171 prio=5 tid=0x000000011f809000 nid=0x2703 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
   JavaThread state: _thread_blocked

此快照可以帮助我们推断有关应用程序在本地和生产中如何运行的许多细节。

  • 我们的代码编译了吗?
  • 它正在等待监视器吗?
  • 还有哪些其他线程正在运行以及它们在做什么?

使用 jmap 进行堆内存分析
对于深入研究 RAM 和堆内存来说,jmap这是无与伦比的。它显示全面的堆内存详细信息,有助于 GC 调整和性能优化。特别有用的是histo通过 RAM 使用直方图识别潜在内存泄漏的标志。

jstackjmap 的典型用法与本文中提到的其他工具非常相似:

$ jhsdb jmap --pid 1234 --heap
Attaching to process ID 1234, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0.13+8-LTS

using thread-local object allocation.
Garbage-First (G1) GC with 9 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 17179869184 (16384.0MB)
   NewSize                  = 1363144 (1.2999954223632812MB)
   MaxNewSize               = 10305404928 (9828.0MB)
   OldSize                  = 5452592 (5.1999969482421875MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 4194304 (4.0MB)

Heap Usage:
G1 Heap:
   regions  = 4096
   capacity = 17179869184 (16384.0MB)
   used     = 323663048 (308.6691360473633MB)
   free     = 16856206136 (16075.330863952637MB)
   1.8839668948203325% used
G1 Young Generation:
Eden Space:
   regions  = 66
   capacity = 780140544 (744.0MB)
   used     = 276824064 (264.0MB)
   free     = 503316480 (480.0MB)
   35.483870967741936% used
Survivor Space:
   regions  = 8
   capacity = 33554432 (32.0MB)
   used     = 33554432 (32.0MB)
   free     = 0 (0.0MB)
   100.0% used
G1 Old Generation:
   regions  = 4
   capacity = 478150656 (456.0MB)
   used     = 13284552 (12.669136047363281MB)
   free     = 464866104 (443.3308639526367MB)
   2.7783193086322986% used

在大多数情况下,这可能看起来像是胡言乱语,但当我们经历 GC 崩溃时,这可能是你武器库中的秘密武器。您可以使用它来微调 GC 设置并确定要设置的正确参数。由于这可以轻松地在生产中运行,因此您可以基于现实世界的观察。

如果您可以重现内存泄漏但没有附加调试器,则可以使用它来生成内存直方图:

$ jhsdb jmap --pid 1234 --histo
Attaching to process ID 72640, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0.13+8-LTS
Iterating over heap. This may take a while...
Object Histogram:

num       instances    bytes    Class description
--------------------------------------------------------------------------
1:        225689    204096416    int[]
2:        485992    59393024    byte[]
3:        17221    23558328    sun.security.ssl.CipherSuite[]
4:        341376    10924032    java.util.HashMap$Node
5:        117706    9549752    java.util.HashMap$Node[]
6:        306720    7361280    java.lang.String
7:        12718    6713944    char[]
8:        113884    5466432    java.util.HashMap
9:        64683    4657176    java.util.regex.Matcher
10:        95612    4615720    java.lang.Object[]
11:        106233    4249320    java.util.HashMap$KeyIterator
12:        16166    4090488    long[]
13:        126977    4063264    java.util.concurrent.ConcurrentHashMap$Node
14:        150789    3618936    java.util.ArrayList
15:        130167    3546016    java.lang.String[]
16:        156237    3227152    java.lang.Class[]
17:        33145    2916760    java.lang.reflect.Method
18:        32193    2575440    nonapi.io.github.classgraph.fastzipfilereader.FastZipEntry
19:        17314    2051672    java.lang.Class
20:        32043    1794408    io.github.classgraph.ClasspathElementZip$1
21:        107918    1726688    java.util.HashSet
22:        105970    1695520    java.util.HashMap$KeySet

这可以帮助缩小问题的根源。在 IDE 和开发过程中,有更好的工具可以实现这一点。但如果您在本地运行服务器,它可以立即为您提供 RAM 快照。

jinfo 的基本 JVM 见解
虽然不像其他命令那么详细,但jinfo对于快速浏览系统属性和 JVM 标志非常有用,尤其是在不熟悉的机器上。这是一个简单的工具,只需要 PID 即可运行。

jhsdb jinfo --pid 1234

jsnap 的性能指标
jsnap提供丰富的内部指标和统计数据,例如线程数和峰值数。这些数据对于微调线程池大小等方面至关重要,直接影响生产开销。

$ jhsdb jsnap --pid 72640
Attaching to process ID 72640, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0.13+8-LTS
java.threads.started=418 event(s)
java.threads.live=12
java.threads.livePeak=30
java.threads.daemon=8
java.cls.loadedClasses=16108 event(s)
java.cls.unloadedClasses=0 event(s)
java.cls.sharedLoadedClasses=0 event(s)
java.cls.sharedUnloadedClasses=0 event(s)
java.ci.totalTime=23090159603 tick(s)
java.property.java.vm.specification.version=11
java.property.java.vm.specification.name=Java Virtual Machine Specification
java.property.java.vm.specification.vendor=Oracle Corporation
java.property.java.vm.version=11.0.13+8-LTS
java.property.java.vm.name=OpenJDK 64-Bit Server VM
java.property.java.vm.vendor=Azul Systems, Inc.
java.property.java.vm.info=mixed mode
java.property.jdk.debug=release

GUI 调试:可视化方法
我们将跳过 CLI 调试器,GUI 调试器值得一提的是其用户友好的界面,允许轻松连接到核心文件、服务器或 PID。这个可视化工具开辟了调试的新维度,特别是在使用 JNI 本机代码时。

GUI 调试器可以像任何其他工具一样启动
jhsdb hsdb --pid 1234

GUI 布局旨在简化导航,提供 JVM 内部结构的全面视图,一目了然。以下是一些主要功能以及如何使用它们:

  • 文件菜单File Menu:这是连接到调试目标的起点。您可以加载核心文件以进行事后分析,附加到正在运行的进程以诊断实时问题,或者如果您正在处理分布式系统,则可以连接到远程调试服务器。
  • 线程和监视器Threads and Monitors:GUI 提供了线程状态的实时视图,从而更容易识别死锁、线程争用和监视器锁。这种可视化表示简化了查明可能影响应用程序性能的并发问题的过程。
  • 堆摘要Heap Summary:对于内存分析,GUI 调试器提供堆使用情况的图形概述,包括生成(用于 GC 分析)、对象计数和内存占用。这使得识别内存泄漏和优化垃圾收集策略更加直观。
  • 方法和堆栈检查Method and Stack Inspection:无缝地深入研究方法执行和堆栈帧,允许您跟踪执行路径、检查局部变量并评估不同时间点的应用程序状态。

总结
jhsdb 是调试工具包中不可或缺的工具,对于处理 JVM 和本地代码问题的人来说尤其如此。从深度内存分析到性能指标,jhsdb 功能丰富,是开发人员和系统管理员的不二之选。

它的最大优势在于调试 Java 代码与本地代码之间的交互。这类代码经常以奇怪的方式在终端用户机器上出现故障。在这种情况下,典型的调试器可能不是最好的工具,也可能无法揭示全貌。尤其是在获得 JVM 内核转储(这正是 jhsdb 的主要用例)时,情况更是如此。