Thread Dump 是 JVM 診斷與效能調優的關鍵工具。傳統方式需登入伺服器執行 kill -3 或 Ctrl+Break,操作不便且難以自動化。本文介紹如何透過 Java 程式碼直接生成 Thread Dump,支援遠端呼叫、定時收集、整合監控系統等進階場景。
package util.threaddump;
import java.util.Map;
/**
* 使用 Java 程式碼遠端生成 Thread Dump,適用於 JDK 1.5+
* 參考: Thread.getStackTrace(), Throwable.getStackTrace()
* @author beansoft@126.com
*/
public class ThreadDumpBuilder {
/**
* 生成並返回完整 Thread Dump 字串
* @return 格式化的執行緒堆疊資訊
*/
public String build() {
StringBuilder output = new StringBuilder(2048);
for (Map.Entry entry : Thread.getAllStackTraces().entrySet()) {
appendThreadStackTrace(output, entry.getKey(), entry.getValue());
}
return output.toString();
}
/**
* 格式化單一執行緒堆疊
* @param output 輸出緩衝區
* @param thread 目標執行緒
* @param stack 堆疊元素陣列
*/
private void appendThreadStackTrace(StringBuilder output, Thread thread, StackTraceElement[] stack) {
// 忽略當前執行緒避免遞迴
if (thread.equals(Thread.currentThread())) return;
output.append(thread).append('\n');
for (StackTraceElement element : stack) {
output.append('\t').append(element).append('\n');
}
}
}
<%@ page import="util.threaddump.ThreadDumpBuilder" %>
<pre><%= new ThreadDumpBuilder().build() %></pre>
@WebServlet("/admin/thread-dump")
public class ThreadDumpServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/plain;charset=UTF-8");
resp.getWriter().write(new ThreadDumpBuilder().build());
}
}
@Scheduled(cron = "0 */30 * * * ?") // 每 30 分鐘
public void collectThreadDump() {
String dump = new ThreadDumpBuilder().build();
logService.save(ThreadDumpLog.builder()
.timestamp(Instant.now())
.content(dump)
.build());
}
Thread[Main Thread,5,main]
java.net.PlainSocketImpl.socketAccept(Native Method)
java.net.PlainSocketImpl.accept(PlainSocketImpl.java:384)
org.apache.catalina.core.StandardServer.await(StandardServer.java:389)
...
Thread[TP-Processor1,5,main]
java.lang.Object.wait(Native Method)
org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:658)
...
"Main Thread" id=1 idx=0x4 tid=1760 prio=5 alive, in native
at java/net/PlainSocketImpl.socketAccept(Native Method)
^-- Holding lock: java/net/SocksSocketImpl@0x017CA398[biased lock]
at java/net/ServerSocket.implAccept(ServerSocket.java:453)
...
"TP-Processor1" id=14 idx=0x3c tid=3184 prio=5 alive, in native, waiting, daemon
-- Waiting for notification on: org/apache/tomcat/util/threads/ThreadPool$ControlRunnable@0x0179EB28[fat lock]
at jrockit/vm/Threads.waitForNotifySignal(JLjava/lang/Object;)Z(Native Method)
...
| 特性 | 程式碼生成 | JVM 原生 (Ctrl+Break/kill -3) |
|---|---|---|
| 鎖資訊 | 無 | 完整(持有鎖、等待鎖、鎖類型) |
| 執行緒狀態 | 基本 | 詳細(native、sleeping、waiting 等) |
| 安全限制 | 受 SecurityManager 限制 | 無 |
| 適用場景 | 遠端、自動化、輕量級檢查 | 深度診斷、死鎖分析、生產事故 |
建議:日常監控與自動化收集使用程式碼方式;疑難雜症、死鎖分析仍需原生 Thread Dump。
public class RemoteThreadDumpClient {
public static String fetchThreadDump(String jmxUrl) throws Exception {
JMXServiceURL url = new JMXServiceURL(jmxUrl);
JMXConnector connector = JMXConnectorFactory.connect(url);
MBeanServerConnection mbsc = connector.getMBeanServerConnection();
ObjectName threadMXBean = new ObjectName("java.lang:type=Threading");
String[] threadIds = (String[]) mbsc.getAttribute(threadMXBean, "AllThreadIds");
StringBuilder dump = new StringBuilder();
for (String tid : threadIds) {
CompositeData cd = (CompositeData) mbsc.invoke(threadMXBean, "getThreadInfo",
new Object[]{Long.parseLong(tid), Integer.MAX_VALUE},
new String[]{"long", "int"});
dump.append(formatThreadInfo(cd)).append("\n");
}
return dump.toString();
}
}
jdk1.5.0/demo/management/FullThreadDumpThreadMXBean 官方文件(JMX 方式獲取更豐富資訊)