如何解决JVM 如何在底层收集 ThreadDump
请解释 JVM 如何在底层收集 ThreadDump。
我不明白它如何收集 cpu 外线程的堆栈跟踪(等待磁盘 IO、网络、非自愿上下文切换)。
例如,linux perf 仅收集有关 cpu 上线程(使用 cpu 周期)的信息
解决方法
我将以 HotSpot JVM 为例。
JVM 维护着所有 Java 线程的列表:对于每个线程,它都有一个对应的 VM 结构。一个线程可以根据其执行上下文处于以下状态之一(HotSpot 知道每个线程的当前状态,因为它负责切换状态):
-
in_Java
- 一个线程正在执行 Java 代码,无论是在解释器中还是在 JIT 编译的方法中; -
in_vm
- 一个线程位于 VM 运行时函数内; -
in_native
- 一个线程正在 JNI 上下文中运行本地方法; - 还有过渡状态,但为了简单起见,让我们跳过它们。
一个off-cpu线程只能有
-
in_native
状态:所有套接字 I/O、磁盘 I/O 和其他阻塞操作仅在本机代码中执行; -
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
的切换上检查挂起的安全点操作:如果本机方法返回,并且 VM 当前正在运行停止世界操作,当前线程阻塞直到操作结束。
因此,获取线程转储涉及
- 在安全点停止所有
in_Java
和in_vm
线程; - 遍历 JVM 维护的全局线程列表;
- 如果一个线程正在运行native方法,它的顶层Java框架存储在一个线程结构中;如果线程正在运行 Java 代码,则其顶部框架对应于当前正在执行的 Java 方法。
- 每一帧都有一个指向前一帧的链接,因此给定顶部帧,JVM 可以构建到底部的整个堆栈跟踪。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。