昨天在公司寫了一段代碼,很簡單,就是測試Thread的dumpStack方法的使用。因為Thread的dumpStack方法不是很常用,但它對于如果想看看誰在運行時調用方法還是非常有幫助的。回到正題,看輸出結果:
java.lang.Exception: Stack trace
at java.lang.Thread.dumpStack(Unknown Source)
at Common.getInfo(TestDumpStack.java:21)
at TestDumpStack.main(TestDumpStack.java:7)
大家可以看到在輸出的第二行,顯示的是at java.lang.Thread.dumpStack(Unknown Source)。為什么自己寫的代碼就顯示出了源碼文件的名字及所在行數,而jdk的類庫就顯示出了Unknown Source?
相信很多人在調試代碼,用log工具打印堆棧異常信息,查看代碼所在行的相關調試信息時,經常會遇到Unknown Source這個頭痛的問題。那么這個東西到底如何而來?
A.Unknown Source從哪來?
Unknown Source,顧名思義,就是未知的源文件。因為我們最終解釋運行的是class文件,所以出現這個問題的原因很簡單,就是class文件中沒有源文件的相關調試信息。那為什么class文件會沒有調試信息呢?答案更簡單,當然是我們在用javac命令進行編譯的時候沒有指定調試信息唄。因為現在很多人都習慣用eclipse等一些現成的ide進行編寫代碼,所以很少人熟悉jdk自己的javac,java,jdb等一些命令的詳細參數(jdk的一些命令和eclipse自帶的一些命令可能不同)。哈哈,不過如果你經常在linux下玩java的話,命令肯定會非常熟悉。那么讓我們看看javac的一些重要參數:
-g-Generate all debugging information, including local variables. By default, only line number and source file information is generated.在class文件中生成所有調試信息,包括局部變量的信息。默認的話,只寫入源碼的行號和源文件信息。
-g:none-Do not generate any debugging information.不生成任何調試信息。
-g:(lines,vars,source)-只生成部分調試信息(源碼行號,變量,源文件信息)。那我們在分別介紹下lines,vars,source的含義。
lines:將源文件中的行號信息寫到Class文件中,此屬性用于在Class文件中生成方法字節碼流偏移量和源代碼行號之間的映射關系。如果我們不指定此屬性的話,我們將在堆棧異常信息中看不到打印的行號。
vars:Local variable屬性建立了方法的棧幀中局部變量部分內容與源代碼中局部變量名稱和描述符之間的映射關系。有了這個屬性,調試時,我們才可以看到變量的值。
source:編譯時指定了這個屬性,會把源文件的屬性信息如源文件名稱寫入class文件。
說了這么多,初學者可能會迷糊,為什么編譯要指定這些調試信息呢?哈哈,如果編譯不指定這些調試信息的話,你怎么調試呢?如果你不指定行號信息的話,你在ide中都無法插入斷點。這些調試信息在我們調試程序的時候非常重要。不過這些編譯選項通常在ide中如eclipse中早已默認了。有的人可能還不相信,打開eclipse,依次打開菜單選項:Window->Preferences->Java->Compiler,可以看到頁面的下方有一個Classfile Generation,默認是四個選項都選的。
那這個Unknown Source到底是編譯的時候沒有指定哪一項呢?經過測試,我發現是javac編譯的時候沒有沒有指定source選項,必定出Unknown Source這個問題。
PS1:linux下,很多人用ant進行javac任務編譯,查看堆棧異常時也經常會遇到Unknown Source的問題。ant編譯時,默認相當于指定-g:none,及不生成任何調試信息的。所以如果要看到日志分析中的源碼和行號信息時,要更改build.xml中的dubug屬性。
PS2:我覺得看看Log4j的日志操作類源碼包會對這個理解更有幫助。
B.剛開始的代碼引子中,為什么自己寫的代碼會有堆棧異常的代碼行數顯示,而jdk的類庫(rt.jar-Runtime Java Archive)代碼會出現Unknown Source?
答案很簡單,因為我們直接用的是jdk直接編譯好的class文件。而rt.jar源碼編譯打包的時候,是沒有將調試信息放入class文件的。所以才會顯示Unknown Source。其實,道理很簡單,sun的類庫正常的情況下肯定不會有bug的,之前肯定都是調試過很多遍的,所以沒有必要再加入調試信息,你只負責用就行了。所以,出現Unknown Source很正常。
PS:其實,我覺得這和軟件的開發版本差不多。版本一般都有dubug版本和release版本。debug版本就是包含調試信息的。不過正式發行后,肯定不包含調試信息的。因為如果包含調試信息的話,可能版本占用空間會很大,而且根本就無需調試信息。
C.如果我們非要對jdk的類庫如rt.jar進行跟蹤調試怎么辦?
因為rt.jar編譯打包的時候,是不包含調試信息的。如果你只是想看看調用的過程,你只需要在eclipse中rt.jar下的Source attachment指定jdk安裝目錄的src.zip即可。不過如果你想跟蹤jdk類庫的變量值的時候,這樣就不行了。除非,只有一種辦法,你重新編譯一下src.zip,指定好編譯參數,然后用新編譯好的rt.jar覆蓋掉原來的rt.jar。這樣就完全ok了。
D.如果我們想debug其他沒有源代碼的class文件呢?
其實,也不難,利用jad等反編譯工具編譯出源碼后,在進行調試。不過前提是該class文件有調試信息。
PS:在網上看到了一些打印堆棧異常信息的代碼,發現有的竟然打印出了jdk源碼的所在行數。如
at java.lang.Thread.dumpStack(Thread.java:1206)等。我覺得很好奇,原因可能是重新編譯了jdk的源碼或者可能用的不同的ide或者不同版本的jdk吧。這個尚需考證。如果有懂的童鞋,可以和我交流。
終于寫完了。可能有很多地方需要改正,希望不吝指教。看了看時間,是凌晨1:46分。不早了,該睡了。天亮說晚安。