Java調試技術

JAVA調試技術


前言


這份材料介紹JAVA的調試技術,範圍涵蓋普通程序和服務器端程序的調試。


很多程序員並沒有認識到排除軟件的錯誤的價值,如果你是一個JAVA開發者,就很值得讀一讀這個材料。在現代工具的幫助下,開發者成爲一個好的調試者和成爲一個好的程序員的重要性一樣。


這個材料假設你已經有基本的JAVA編程的知識,如果你精通JAVA,這個材料也可以增加你很多知識。


如果你有其他語言的調試經驗,你可以跳過基本知識部分。


即使是高級程序員開發的小程序也可能包含錯誤。你只需要理解調試的概念並熟悉合適的工具就可以成爲好的調試者。這份材料將講解JAVA調試的基本概念,也討論高級的調試類型。我們將瀏覽不同的技術並且提供一些好的建議去幫助避免,追蹤並最終修正程序的錯誤。


我們將通過一個調試範例以使你熟悉調試技術。我們也將使用開發源代碼工具Jikes JDB向你演示如何調試服務器端和客戶端程序。爲了編譯和運行範例代碼,你需要先安裝一個Java Development Kit (JDK) ,你可以參考後面的部分獲得Jikes JDB調試器。


關於作者


如果對這個材料的內容有任何問題,你可以聯繫作者Laura Bennett, [email protected]


如果對中文版的翻譯有何意見和建議,請聯繫翻譯者cherami ,[email protected]


Laura Bennett IBM的資深軟件工程師。她獲得Pace大學的計算機科學學士學位和Columbia大學的計算機科學碩士學位。她是developerWorksJAVA傳教士,同時也是站點的建設者。在他的空餘時間,她喜歡和她的Lego MindStorm 機器人玩樂以及和她四歲大的TinkerToys搭建物體。


Cherami是一個軟件工程師,閒暇之餘翻譯一些計算機文獻,以期爲中國的計算機軟件事業做出一點微薄的貢獻。


 


 


調試的基礎知識


開始的情況


JAVA語言的早期,一個典型的開發者使用非常陳舊的方法調試程序:使用System.out.println() 方法。代碼的跟蹤信息被打印到控制檯、文件或者套接字。


很少有人能在第一次就寫出完美的(沒有任何錯誤)代碼。因此,市場認識到了對於像C++ 程序員使用的調試器那樣的工具的需要。Java開發者現在有很多調試工具可以選擇,選擇什麼樣的工具依賴於你的技術等級。通常新手使用GUI調試工具而有更多經驗的程序員趨向於避免使用所見即所得的工具而更關心有更多的控制能力。沒有哪個開發者不使用任何調試工具。調試器允許你穿越代碼,凍結輸出以及檢查變量。開發者越有經驗,調試工具越可以幫助他更快定位程序問題的位置。


Java調試器的類型


這裏有幾種Java調試技術的工具:



  • IDE(集成開發環境) 包含它們自己的調試器 (例如IBMVisualAge for Java, Symantec Visual Café以及 Borland JBuilder)


  • 單獨的GUI工具 (例如Jikes, Java 平臺調試器 javadt, 以及JProbe)


  • 基於文本和命令行的工具 (例如Sun JDB)


  • 野蠻的使用編輯器 (例如Notepad 或者 VI) 檢查堆棧描繪(stack traces


你使用的 JDK, JSDI, JSP, HTML對你的選擇都有影響。


IDE 和獨立的GUI 調試器對於初學者是最容易的並且被證明是最節省時間的。調試器將引導你到程序崩潰的地方。在調試器裏面執行程序,使用鼠標設置斷點並穿越代碼。使用這些調試器的不利方面是並非所有的IDE調試器都支持最新的Java API和技術 (例如servlets EJB 組件)


基於文本和野蠻的使用編輯器的技術提供更多的控制但是對於沒有太多經驗的程序員可能會花費更長的時間找出錯誤。我們稱它們爲“可憐人的”調試方法。


如果上面的都不滿足你的需求, Java平臺引入Java Debugging APIs使你可以創建符合你自己特定需求的調試器。


調試類型


這兒有很多調試方法,無論是在客戶端還是服務器端。我們在這個材料裏面包含下面的方法:



  • 基本的Java字節碼 (也就是使用System.out.println())


  • 使用註釋


  • 附加在一個正在運行的程序上


  • 遠程調試


  • 需求調試(Debugging on demand


  • 優化代碼的調試


  • Servlet, JSP 文件以及EJB 組件的調試


在後面會詳細說明每一種類型的調試。


共同的錯誤類型


爲了給你一個你將遇到什麼的提示,我們在下面列出了開發者一次又一次遇到的普遍錯誤:



  • 編輯或句法錯誤 是你最先和最容易遇到的錯誤。它們通常是鍵入錯誤引起的。


  • 邏輯錯誤 不同於運行時錯誤,因爲沒有任何異常被拋出,但是輸出不是期望的東西。這些錯誤的範圍從緩衝區溢出到內存泄漏。


  • 運行時錯誤 在程序執行時發生並且通常產生一個Java異常。


  • 線程錯誤 是最難重複和跟蹤的。


Java debugging APIs


Sun已經定義了調試的結構,它們稱之爲JBUG。這是爲了迴應對真正的Java調試器的需要做出的。這些APIs幫助程序員建立符合自己需要的調試器:



  • 接口應該和語言的風格一樣是面向對象的。


  • 例如線程和監視器這樣的Java運行時特性應該被前面的支持。


  • 可以進行遠程調試。


  • 在通常操作下的安全性不能被損害。


修正的Java Debugger (JDB) 既是體現Java Debugging API的概念,同時又是一個有用的調試工具。它用Java Debug Interface (JDI)重寫並且是JDK的一部分。 JDB將在後面詳細討論。


準備一個調試用的程序


Java平臺爲調試過程提供語言支持。


你在用編譯器編譯你的程序時可以用編譯選項指示編譯器在目標文件中產生符號信息。如果你使用其它的編譯器而不是javac,參考你的編譯器的文檔獲得如何生成帶有調試信息的目標文件。


如果你使用javac 編譯器創建調試代碼,使用-g 編譯選項。這個選項讓你在調試的時候可以檢查本機類實例和靜態變量。如果你沒有使用該選項生成你的類文件你也可以設置斷點和追蹤代碼,但是你將不能檢查變量。(斷點是手工指定的程序運行停止的點。)


即使你使用-g選項編譯你的程序也不能調試JAVA平臺的核心繫統類的局部變量。如果你需要列出某些系統類的局部變量的列表,你需要使用-g選項編譯這些類,也就是使用-g選項重新編譯rt.jar 的類或者是 src.zip 裏面的文件。然後指定你的 classpath 爲正確的類文件使你用新編譯的類運行你的程序。在Java 2下,使用 boot classpath 選項使得新類被首先加載。


記住如果你使用 -O 選項優化你的代碼,你就不能調試你的類。優化會將所有的調試信息從類中去掉。


注意: 檢查你的 CLASSPATH 環境變量是正確的才能讓調試器和Java 程序知道在哪兒尋找你的類庫。你也應該檢查你的調試工具看是否需要其它的什麼或者是環境變量。


設置斷點


調試的第一步就是找到代碼出錯的位置。斷點設置能幫你完成這個。


斷點是你你放置在程序裏面的臨時標記,它使得調試器知道在哪兒停止程序的執行。例如,如果程序裏面的某個申明引發問題,你可以將斷點設置在包含那個申明的行上,然後運行程序。在那個申明被執行前程序停止執行。然後你可以檢查變量、寄存器,存儲器以及堆棧的內容,然後跨過(或執行)那個申明查看問題是怎麼引起的。


不同的調試器支持不同的斷點。一些通用的類型是:



  • 行斷點 在程序特定行的代碼被執行前被引發。


  • 方法斷點 在到達被設置成斷點的方法時被引發。


  • 計數斷點 在某個計數器達到或超過某個特定值時被引發


  • 異常斷點 在代碼拋出一個特定異常時被引發


  • 儲存變化斷點 在存儲在特定地址範圍的內容被修改時引發


  • 地址斷點 在被設置成斷點的地址達到時被引發


注意: 一些調試器只在編譯版本的Java代碼 (使用just-in-time 編譯器生成的代碼) 上支持某些斷點類型而不支持解釋代碼(使用javac 工具生成的代碼)。一個例子就是地址斷點。每個工具在你能設置斷點的方式上可能有些不同。檢查你的工具的文檔。


你可能會問,我如何知道在哪兒放置斷點?



  • 如果你對這個問題完全沒有感覺,你可以在main() 方法的開始設置斷點


  • 如果你的代碼產生堆棧複寫(stack trace, 在程序產生它的地方設置斷點。你將在堆棧複寫裏面看到源代碼中出問題的行號。


  • 如果你的輸出或者圖形顯示的特定部分沒有正確的顯示預定信息(例如文本域顯示錯誤的文本),你可以在該組件被創建的地方設置斷點。然後你可以單步執行你的程序顯示和GUI對象相關的值。


經驗將在最合適的地方設置斷點。你在一個類或者程序裏面可以設置多個斷點。


通常,你在調試代碼的時候會禁止、激活、添加、刪除斷點。工具會允許你查看你所設置的所有斷點的位置同時給你一次刪除所有斷點的選項。


單步執行程序


單步執行程序是最終解決那些棘手的調試問題的方法。它允許你追蹤類裏面的方法體的整個執行過程。注意,你不需要設置斷點就可以停止一個GUI程序的執行。


設置斷點後在調試器裏面開始執行程序,當遇到第一個斷點後,你可以越過申明,進入方法體或類體,也可以繼續運行直到下一個斷點或程序結束。


在調試程序的時候經常遇到的術語有:



  • 進入 執行當前行。如果當前行包含一個方法調用,執行被調用方法的第一行。如果類中的方法是用不帶調試信息的選項編譯的 (也就是沒有使用 -g 選項), 你將看到No Source Available 消息。


  • 越過 執行當前行而不會因爲該行調用了一個方法或例程而停止。


  • 返回 從當前執行點執行並立即返回到調用當前方法的行。

檢查變量


通常,程序會因爲一個變量的值沒有正確設置而進行核心轉儲(core dump)。最常見的是試圖進行一個值爲null 的計算或比較以及除零。找出這種問題的最簡單的辦法是在錯誤發生的地方檢查變量的值。最通常的情況是變量在那點沒有得到預期分配的值。


可視化調試器通常有一個監視窗口顯示你當前正在執行的類的所有局部變量的值。某些調試器甚至顯示變量的地址或更進一步的允許你動態的改變變量的值以查看如果值是你原來預想的情況時程序是否能繼續執行。命令行調試器通常提供命令提供相應的特性。使用命令行特性,你甚至可以通過顯示數組的每一行和每一列的內容來查看整個數組。


雖然大多調試器只在監視窗口顯示類裏面的局部變量,還是有一些調試器允許你在變量超出範圍後繼續監視它。


一些調試器支持查看寄存器。注意這隻能是查看編譯的Java 程序而不能是解釋的程序(字節碼程序)。


堆棧複寫(Stack traces


Java 程序進行內核轉儲(core dumps)時它在控制檯產生我們稱之爲堆棧複寫(stack trace 的東西。堆棧複寫告訴開發者程序發生問題的精確路徑。它將說明類和方法名以及源代碼中的行數 (如果你使用調試選項編譯)。如果你在發生堆棧複寫的開始處開始調試並停下,你可以向後查看你的代碼看看實際上是什麼申明被執行了。這是一個快速發現程序問題的辦法。


你也可以使用下面的一個方法手動強制產生堆棧複寫。



  • Throwable().printStackTrace() 在調用該方法的那個點產生堆棧複寫。複寫將顯示方法調用所涉及到的線程。


  • Thread.currentThread.dumpStack() 只產生當前線程的一個快照。


當你需要理解在什麼條件下你的程序會產生堆棧複寫時使用強制複寫。下面的程序是一個強制堆棧複寫的例子。這個程序片斷進行文件拷貝。我們通過比較兩個文件的長度是否相等來判斷拷貝是否成功。如果不相等,我們向文件寫入複寫然後強制打印堆棧複寫(參看黑體的申明)。Throwable() java.lang 中的一個類, printStackTrace() Throwable() 的一個方法,它打印程序執行路徑的複寫。

 

public static boolean copyFile( String sourceFile, String targetFile)

{                                                                     

   ........

   ........

   // see if the copy succeeded.

   if (success)

   {

     // see if the correct number of bytes were copied

     long newFileLength = new File(targetFile).length();

     if (oldFileLength != newFileLength)

     {

       Debug.trace(1, sourceFile + Constants.BLANK_STRING + Long.toString(oldFileLength));

       Debug.trace(1, targetFile + Constants.BLANK_STRING + Long.toString(newFileLength));

       Throwable().printStackTrace();

       return false;

     }

   }

   else

     {

       Debug.trace(1, sourceFile);

       Debug.trace(1, targetFile);

       return false;

     }

  ........

  ........

  return true;

}


你可能會發現堆棧複寫中沒有行號。這可以簡單的稱爲“編譯代碼”,要產生行號,使用 nojit選項或者 Djava.compiler=NONE命令行參數 禁止JIT 編譯器。如果你知道了方法和改方法所屬類的名字,行號就不那麼重要了。


診斷方法


Java 語言在Runtime() 類中提供方法跟蹤你對JVM的方法調用。這些跟蹤將產生你對JVM字節碼的每一個方法調用的列表。注意這個列表可以產生大量的輸出,所以在你的代碼的小部分裏面使用。


打開跟蹤可以在代碼中加入下面的行:

traceMethodCalls(true)


關閉使用:

traceMethodCalls(false)

打開JVM 並觀察它向標準輸出的輸出。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章