異常對象在創建時會捕獲當前線程的方法調用堆棧快照。若嘗試復用同一異常實例(如定義為 static final 常量),拋出時將輸出創建時的堆棧,而非實際拋出位置,嚴重誤導問題定位。本文通過實例代碼深度剖析此陷阱,並給出正確實踐原則。
public final class MyExceptionContext {
// 類加載時一次性創建,堆棧定格在 階段
public static final MyException x1 = new MyException("X1");
public static final MyException x2 = new MyException("X2");
}
測試代碼在四個不同方法中拋出 x1、x2,但輸出的堆棧完全一致,均指向 MyExceptionContext.,且 x11 和 x21 明明拋的是同一異常,堆棧卻出現有無差異。
fillInStackTrace(),捕獲當前線程堆棧。異常常攜帶 cause(根因),若復用異常對象,cause 也會被固化,無法反映不同場景下的真實鏈路。且異常對象本身可變(如 setStackTrace),多線程復用更會引發競態條件。
// ❌ 錯誤:靜態常量復用
throw MyExceptionContext.x1;
// ✅ 正確:現場實例化
throw new MyException("X1");
// ✅ 帶 cause 保留原始異常
throw new MyException("業務失敗", originalException);
new Exception(),保證堆棧與業務上下文一致。cause,保留完整因果鏈。核心口訣:異常即現場,現場不可復制,拋出必須重建。
理解此機制,能避免生產環境中「堆棧指向類加載器」的誤導性日誌,大幅縮短故障定位時間。