圖形化 IDE 的調試工具擅長單線程 GUI 應用,但難以應對多線程 Java 伺服器程式的動態並發行為。幸運的是,我們擁有強大的診斷工具:日誌 API(如 Log4j)、Java Debugger (jdb)、以及最直接的線程堆棧快照。
在伺服器運行時,於終端按 Ctrl+\(Windows/Linux)或發送 SIGQUIT 信號(`kill -3
Full thread dump:
"Thread-1" prio=5 tid=0x87fe0 nid=0x800013b8 waiting on monitor [0..0xbfffe9f8]
"Thread-0" prio=5 tid=0xafde0 nid=0x72a00 runnable [0x173f000..0x173fb48]
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:421)
at java.net.ServerSocket.implAccept(ServerSocket.java:243)
at java.net.ServerSocket.accept(ServerSocket.java:222)
at com.generationjava.net.PortListener.run(PortListener.java:38)
at java.lang.Thread.run(Thread.java:496)
"Signal Dispatcher" daemon prio=10 tid=0xa5f80 nid=0x52af0 waiting on monitor [0..0]
"Finalizer" daemon prio=8 tid=0xa46c0 nid=0x4d930 waiting on monitor [0x10a8000..0x10a8b48]
at java.lang.Object.wait(Native Method)
...
"VM Thread" prio=5 tid=0xa2de0 nid=0x4ca20 runnable
"VM Periodic Task Thread" prio=10 tid=0xa56b0 nid=0x52890 waiting on monitor
後四條為 JVM 內建守護線程,前兩條為應用程式線程,預設命名為 Thread-0、Thread-1,難以識別業務用途。
建立線程時指定名稱,或事後調用 setName():
// 建立時命名
Thread th = new Thread(runnable, "Server-Listener");
// 或後續命名
thread.setName("Order-Processor-" + orderId);
再次獲取堆棧時,輸出將變為:
"Server-Listener" prio=5 tid=0xafde0 nid=0x72a00 runnable [0x173f000..0x173fb48]
at java.net.PlainSocketImpl.socketAccept(Native Method)
...
原 Thread-1 變為 Thread-0,但關鍵業務線程已清晰可辨。
waiting on monitor 且持有鎖,疑似死鎖。線程堆棧是靜態快照,動態分析需結合:
掌握線程命名與堆棧分析,是 Java 伺服器端開發者排查生產環境疑難雜症的必備技能。