JVM 如何收集 ThreadDump 底层
How JVM collect ThreadDump underhood
请解释 JVM 如何收集底层的 ThreadDump。
我不明白它如何收集关闭的线程的堆栈痕迹-CPU(等待磁盘 IO、网络、非自愿上下文切换)。
例如,linux perf 仅收集有关 on-CPU 线程(使用 CPU-周期)
的信息
我将以 HotSpot JVM 为例。
JVM 维护所有Java 线程的列表:对于每个线程,它都有一个对应的VM 结构。线程可以根据其执行上下文处于以下状态之一(HotSpot 知道每个线程的当前状态,因为它负责切换状态):
in_Java
- 线程正在执行 Java 代码,在解释器或 JIT 编译方法中;
in_vm
- 线程在 VM 运行时函数内;
in_native
- 线程是 运行 JNI 上下文中的本机方法;
- 也有过渡状态,但为了简单起见,我们跳过它们。
一个关闭-cpu线程只能有
in_native
状态:所有socketI/O,磁盘I/O,其他阻塞操作只在native代码中执行;
in_vm
状态,当线程在 VM 互斥体上被阻塞时。
每当 JVM 调用本机方法或获取争用的互斥锁时,它会将最后一个 Java 帧指针存储到 Thread
结构中。
现在是关键部分:HotSpot JVM 仅在 safepoint.
处获取线程转储
当您请求线程转储时,JVM 请求停止世界暂停。 in_Java
状态下的所有线程都停止在最近的安全点,JVM 知道如何遍历堆栈。
处于 in_native
状态的线程不会停止,但它们不需要。 HotSpot 知道他们最后的 Java 帧,因为指针存储在 Thread
结构中。知道顶部 Java 框架,JVM 可以找到它的调用者,然后是调用者的调用者,依此类推。
这里重要的是堆栈的 Java 部分是“冻结的”,无论本机方法做什么。堆栈的顶部(本机)可以来回更改,而底部 (Java) 保持不变。它不能更改,因为 JVM 在从 in_native
到 in_Java
的每个切换上检查挂起的安全点操作:如果本机方法 returns,并且 VM 当前是 运行 stop-the-world 操作,当前线程阻塞直到操作结束。
因此,获取线程转储涉及
- 在安全点停止所有
in_Java
和 in_vm
线程;
- 遍历 JVM 维护的全局线程列表;
- 如果一个线程是运行native方法,它的顶部Java帧存储在一个线程结构中;如果线程是 运行 Java 代码,它的顶部框架对应于当前正在执行的 Java 方法。
- 每一帧都有一个 link 到前一帧,因此给定顶层帧,JVM 可以构建到底部的整个堆栈跟踪。
请解释 JVM 如何收集底层的 ThreadDump。
我不明白它如何收集关闭的线程的堆栈痕迹-CPU(等待磁盘 IO、网络、非自愿上下文切换)。
例如,linux perf 仅收集有关 on-CPU 线程(使用 CPU-周期)
我将以 HotSpot JVM 为例。
JVM 维护所有Java 线程的列表:对于每个线程,它都有一个对应的VM 结构。线程可以根据其执行上下文处于以下状态之一(HotSpot 知道每个线程的当前状态,因为它负责切换状态):
in_Java
- 线程正在执行 Java 代码,在解释器或 JIT 编译方法中;in_vm
- 线程在 VM 运行时函数内;in_native
- 线程是 运行 JNI 上下文中的本机方法;- 也有过渡状态,但为了简单起见,我们跳过它们。
一个关闭-cpu线程只能有
in_native
状态:所有socketI/O,磁盘I/O,其他阻塞操作只在native代码中执行;in_vm
状态,当线程在 VM 互斥体上被阻塞时。
每当 JVM 调用本机方法或获取争用的互斥锁时,它会将最后一个 Java 帧指针存储到 Thread
结构中。
现在是关键部分:HotSpot JVM 仅在 safepoint.
处获取线程转储当您请求线程转储时,JVM 请求停止世界暂停。 in_Java
状态下的所有线程都停止在最近的安全点,JVM 知道如何遍历堆栈。
处于 in_native
状态的线程不会停止,但它们不需要。 HotSpot 知道他们最后的 Java 帧,因为指针存储在 Thread
结构中。知道顶部 Java 框架,JVM 可以找到它的调用者,然后是调用者的调用者,依此类推。
这里重要的是堆栈的 Java 部分是“冻结的”,无论本机方法做什么。堆栈的顶部(本机)可以来回更改,而底部 (Java) 保持不变。它不能更改,因为 JVM 在从 in_native
到 in_Java
的每个切换上检查挂起的安全点操作:如果本机方法 returns,并且 VM 当前是 运行 stop-the-world 操作,当前线程阻塞直到操作结束。
因此,获取线程转储涉及
- 在安全点停止所有
in_Java
和in_vm
线程; - 遍历 JVM 维护的全局线程列表;
- 如果一个线程是运行native方法,它的顶部Java帧存储在一个线程结构中;如果线程是 运行 Java 代码,它的顶部框架对应于当前正在执行的 Java 方法。
- 每一帧都有一个 link 到前一帧,因此给定顶层帧,JVM 可以构建到底部的整个堆栈跟踪。