0.前言
DDR(直接转储读取器)是 DTFJ(Java 诊断工具框架)API 的 Java 实现。它通过遍历转储中的 J9 结构来提取虚拟机和应用程序状态。它对于在程序执行的某个点检查 Java 对象很有用。例如,您可以列出所有线程并检查线程的堆栈跟踪。
在 Java 19 中,Project Loom 引入了虚拟线程。虚拟线程是一种轻量级实现,java.lang.Thread允许人们编写高度并发的应用程序。与平台线程相比,虚拟线程不与任何操作系统线程绑定,而是挂载在平台线程上。如果遇到阻塞操作,则可以卸载虚拟线程,然后其继续不再与任何操作系统线程相关联。
随着越来越多的应用程序使用虚拟线程,在 DDR 中支持虚拟线程对于调试具有虚拟线程的程序来说将变得至关重要。为了支持虚拟线程,添加了三个新的 DDR 命令:vthreads和continuationstack。continuationstackslots
1.一个例子
我们将通过以下示例介绍新命令:
public static void main(String[] args) throws InterruptedException {
Thread t1 = Thread.ofVirtual().name("vthread 1").start(() -> {
System.out.println("virtual thread 1 starts");
LockSupport.park();
System.out.println("virtual thread 1 ends");
});
while (t1.getState() != Thread.State.WAITING) {
Thread.sleep(500); // let vthread 1 park
}
Thread t2 = Thread.ofVirtual().name("vthread 2").start(() -> {
System.out.println("virtual thread 2 starts");
com.ibm.jvm.Dump.systemDumpToFile();
LockSupport.unpark(t1);
System.out.println("virtual thread 2 ends");
});
t1.join();
t2.join();
}
在示例中,创建了两个虚拟线程,其中一个处于暂停状态,因此被卸载。
为了获取所需的核心文件,您可能需要使用选项运行示例-Xdump:system:request=exclusive+prepwalk。
2.vthreads命令
该vthreads命令列出了核心文件中的所有虚拟线程。
让我们用 打开生成的核心文件,并通过键入来jdmpview调用命令,我们得到输出:vthreads!vthreads
> !vthreads
!continuationstack 0x00007f1d94003410 !j9vmcontinuation 0x00007f1d94003410 !j9object 0x00000007FFF9A848 (Continuation) !j9object 0x00000007FFF9A7A0 (VThread) - vthread 2
!continuationstack 0x00007f1d94010110 !j9vmcontinuation 0x00007f1d94010110 !j9object 0x00000007FFF8C598 (Continuation) !j9object 0x00000007FFF8C490 (VThread) - vthread 1
每行列出了四个命令:
- 检查延续的堆栈跟踪
- 检查本机延续结构
- 检查延续对象
- 检查虚拟线程对象
- 虚拟线程的名称也会显示在行末(如果设置了名称)。
该vthreads命令通常是打开核心文件后要执行的第一步。您可以通过查看名称或检查堆栈跟踪(我们将在下一节中介绍)来识别要处理的虚拟线程。
和命令continuationstackcontinuationstackslots
堆栈跟踪对于在崩溃后或在程序执行的某个点快速获取诊断信息很有用。该continuationstack命令输出延续的堆栈跟踪。运行!continuationstack 0x00007f1d94003410(虚拟线程 2 的延续)将输出:
> !continuationstack 0x00007f1d94003410
<7f1d94003410> !j9method 0x00000000000A58F0 jdk/internal/vm/Continuation.enterImpl()Z
<7f1d94003410> !j9method 0x00000000000A5770 jdk/internal/vm/Continuation.run()V
<7f1d94003410> !j9method 0x0000000000096118 java/lang/VirtualThread.runContinuation()V
<7f1d94003410> !j9method 0x000000000026D388 java/lang/VirtualThread$$Lambda/0x0000000000000000.run()V
<7f1d94003410> !j9method 0x000000000026F388 java/util/concurrent/ForkJoinTask$RunnableExecuteAction.exec()Z
<7f1d94003410> !j9method 0x000000000026E108 java/util/concurrent/ForkJoinTask.doExec()I
<7f1d94003410> !j9method 0x000000000025E440 java/util/concurrent/ForkJoinPool$WorkQueue.topLevelExec(Ljava/util/concurrent/ForkJoinTask;Ljava/util/concurrent/ForkJoinPool$WorkQueue;)V
<7f1d94003410> !j9method 0x000000000025BC10 java/util/concurrent/ForkJoinPool.scan(Ljava/util/concurrent/ForkJoinPool$WorkQueue;II)I
<7f1d94003410> !j9method 0x000000000025BBF0 java/util/concurrent/ForkJoinPool.runWorker(Ljava/util/concurrent/ForkJoinPool$WorkQueue;)V
<7f1d94003410> !j9method 0x00000000001FFF30 java/util/concurrent/ForkJoinWorkerThread.run()V
<7f1d94003410> JNI call-in frame
<7f1d94003410> Native method frame
请注意,这实际上是载体线程的堆栈跟踪。这是因为虚拟线程 2 已挂载,并且在挂载过程中,延续的某些变量(例如堆栈指针)与载体线程的变量进行了交换。因此,该命令将continuationstack输出已挂载虚拟线程的载体线程的堆栈跟踪。要查看虚拟线程的堆栈跟踪,请使用stack载体线程的命令:
> !stack 0x0023d700
<23d700> !j9method 0x0000000000286A20 com/ibm/jvm/Dump.triggerDumpsImpl(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
<23d700> !j9method 0x00000000002868C0 com/ibm/jvm/Dump.triggerDump(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
<23d700> !j9method 0x00000000002867C0 com/ibm/jvm/Dump.systemDumpToFile()Ljava/lang/String;
<23d700> !j9method 0x0000000000246800 VTTest2.lambda$main$1(Ljava/lang/Thread;)V
<23d700> !j9method 0x0000000000246DE8 VTTest2$$Lambda/0x000000001436ed78.run()V
<23d700> !j9method 0x00000000000961D8 java/lang/VirtualThread.runWith(Ljava/lang/Object;Ljava/lang/Runnable;)V
<23d700> !j9method 0x00000000000961B8 java/lang/VirtualThread.run(Ljava/lang/Runnable;)V
<23d700> !j9method 0x000000000026C770 java/lang/VirtualThread$VThreadContinuation$1.run()V
<23d700> !j9method 0x00000000000A5750 jdk/internal/vm/Continuation.enter(Ljdk/internal/vm/Continuation;)V
<23d700> JNI call-in frame
<23d700> Native method frame
对于未挂载的虚拟线程,该continuationstack命令将输出虚拟线程的堆栈跟踪:
> !continuationstack 0x00007f1d94010110
<7f1d94010110> !j9method 0x00000000000A5910 jdk/internal/vm/Continuation.yieldImpl(Z)Z
<7f1d94010110> !j9method 0x00000000000A57B0 jdk/internal/vm/Continuation.yield0()Z
<7f1d94010110> !j9method 0x00000000000A5790 jdk/internal/vm/Continuation.yield(Ljdk/internal/vm/ContinuationScope;)Z
<7f1d94010110> !j9method 0x0000000000096298 java/lang/VirtualThread.yieldContinuation()Z
<7f1d94010110> !j9method 0x0000000000096378 java/lang/VirtualThread.park()V
<7f1d94010110> !j9method 0x00000000000B9198 java/lang/Access.parkVirtualThread()V
<7f1d94010110> !j9method 0x0000000000278110 jdk/internal/misc/VirtualThreads.park()V
<7f1d94010110> !j9method 0x000000000008F2C8 java/util/concurrent/locks/LockSupport.park()V
<7f1d94010110> !j9method 0x0000000000246820 VTTest2.lambda$main$0()V
<7f1d94010110> !j9method 0x0000000000246AE8 VTTest2$$Lambda/0x000000001436ec28.run()V
<7f1d94010110> !j9method 0x00000000000961D8 java/lang/VirtualThread.runWith(Ljava/lang/Object;Ljava/lang/Runnable;)V
<7f1d94010110> !j9method 0x00000000000961B8 java/lang/VirtualThread.run(Ljava/lang/Runnable;)V
<7f1d94010110> !j9method 0x000000000026C770 java/lang/VirtualThread$VThreadContinuation$1.run()V
<7f1d94010110> !j9method 0x00000000000A5750 jdk/internal/vm/Continuation.enter(Ljdk/internal/vm/Continuation;)V
<7f1d94010110> JNI call-in frame
<7f1d94010110> Native method frame
stack此外,与和命令类似stackslots,我们还有continuationstackslots随堆栈跟踪一起输出插槽的命令。