Bug(俗稱"八阿哥") 是軟件開發繞不過的一道坎,因此調試便成了每位程序員一項必備的核心技能。調試不僅有助于理解程序的運行流程,還能改進代碼質量,最終提高開發者解決問題的能力以及交付軟件的品質。
本文旨在討論 Java 調試關鍵技巧,同時也會介紹生產環境中調試 Java 的最佳實踐。
0 調試 Java 項目
本節討論 Java 項目調試中的常見概念與技巧,包括Breakpoint (斷點)、Step filter (單步過濾器)、Drop to Frame (跳出函數到選定層)等。這些技術同樣可以在復雜場景下使用。
條件斷點
斷點用來指定在調試過程中程序停止執行的位置。通過臨時掛起執行過程,可以觀察或修改字段與變量值。下面的內容雖然使用了 Eclipse進行演示,但是相關概念同樣適用于其他 Java IDE。條件斷點即設置成在滿足特定條件時觸發的斷點,用來檢查條件發生時程序的狀態、調用堆棧及重要的變量值。
下面的示例對音樂專輯得分計算算術平均值:

假設有數百張專輯,可以在循環中設置條件斷點 album.getRating().score == 0。當出現專輯得分為0時,程序就會停止執行。
其他斷點類型
除了上面介紹的斷點類型,不同的 Java IDE 還提供了其他類型的斷點:
- 事件斷點:與事件綁定,在遇到調試器能夠識別的事件時觸發;
- 字段斷點:給定字段或表達式的值發生變化時,字段斷點將停止正在執行的程序。調試時可以指定一個字段作為觀察點,表達式讀取、修改時停止執行;
- 方法斷點 :進入或退出指定方法時掛起程序,用來檢查特定方法的進入或退出事件;
- 行斷點:程序到達斷點中設置的特定代碼行時停止程序執行。
使用 Rookout 設置斷點
Rookout 是一個創新的調試平臺,在不停止或中斷程序的情況下搜集調試數據。支持自定義斷點觸發條件。Rookout 提供的"斷點狀態"功能非常強大:在斷點附近通過警告標志或 展示斷點狀態。
Rookout 有5種斷點狀態:
- Active (紫色, 實心)
- Pending (紫色, 空心)
- Warning (紫色, 實心, 帶三角形)
- Error (紫色, 空心, 帶三角形)
- Disabled (灰色, 空心)

"斷點狀態" 可以在查看日志或調試信息前就能對程序運行狀況有所了解。
Step Filter (單步過濾器)
Step Filter 可以在調試中指定需要跳過的 package。在調試多個 class、第三方庫或框架時非常好用。在 Eclipse 中可以通過 Window Preferences >>Java Debug >>Step Filtering 配置。
Drop to frame (跳出函數到選定層)
這種技術可以在調試期間選擇并重新執行程序的一部分:指定程序調用堆棧中任意幀(frame),在 Debug View 中點擊 Drop-to-frame 按鈕,調試器會從這里重新啟動。Drop to frame 不會影響字段或現有數據,例如已寫入數據庫的記錄。
1 遠程調試
大多數 Java IDE 都支持 JVM 遠程調試。設置調試參數如下:

Eclipse 用戶需輸入主機名 (hostname) 和端口號,如下圖所示:

2 在生產環境調試 Java
現如今開發節奏越來越快,代碼發布也是如此。無論測試流程如何嚴格,總會有漏網之魚。當這些漏洞遇到生產中的實際數據,產生的壓力隨之飆升。因此,進行系統擴展的同時解決潛在的漏洞非常關鍵。
下面的策略可供每個 Java 開發者參考:
- 確定錯誤發生的時間;
- 評估錯誤的嚴重程度(優先級);
- 篩選定位導致程序出錯的狀態;
- 接下來,跟蹤并解決根本原因(root cause);
- 最后,打補丁。
除了上面的五個步驟,還應當遵守下面這些生產環境最佳實踐:
提高日志等級
大多數情況下,錯誤信息沒有包含足夠的上下文內容,因此調試時需要提升日志等級。完整的上下文內容可以有效地理解、定位和確定錯誤的根本原因。一種常見的方法,在每個線程的入口點生成 UUID。
理想情況下,可以按照以下格式設置線程名稱:

這樣,堆棧跟蹤信息會以 “threadName: pool-7-thread-22, UUID: EB85GTA, MsgType: AnalyzeGraph, MsgID: 415669, 29/03/2020 04:44” 開頭,比起 “pool-7-thread-22” 這樣的名字更有意義。
集中日志
在應用程序生命周期各階段尤其是生產階段處理錯誤時,應優先考慮高效的日志記錄機制。把會話中所有重要事件匯集到統一的日志服務器,不但能夠降低調試的難度,而且在跟蹤關鍵產品指標時,還可以幫助監視應用程序中發生錯誤的情況。
檢查堆棧跟蹤和其他日志
調試異常時,堆棧跟蹤非常有用:它能幫助確定在程序崩潰時調用了哪些函數以及調用順序。下面的代碼使用 printStack() 方法打印異常堆棧:

輸出結果:

復制實例
獲取日志后,接下來最重要的是在復制場景(實例)。通常會創建一個與 IDE 調試類似的環境,便于分析與解決錯誤。
3 總結
Java 調試并不是開發者的噩夢。一點創造性思維加上合適的工具,開發者會更有信心、更加快速準確地識別、診斷和解決代碼中的錯誤。