Java 異常跟蹤棧用來記錄異常拋出的完整流程。從某種角度看,Java 程序的執行過程其實就是方法之間的相互調用過程,Java 異常的拋出流程和方法的調用過程恰好相反:
Java 方法從上層到下層順序調用;
Java 異常從下層到上層逆向拋出。
異常對象的 printStackTrace() 方法用于打印異常的跟蹤棧信息,根據 printStackTrace() 方法的輸出結果,開發者可以找到異常的源頭,并跟蹤到異常一路觸發的過程。
下面的 printStackTrace 類用來觀測 Java 異常的拋出過程:
上面程序中 main 方法調用 firstMethod,firstMethod 調用 secondMethod,secondMethod 調用 thirdMethod,thirdMethod 直接拋出一個 SelfException 異常。運行上面程序,會看到如下所示的結果。
上面運行結果的第 2 行到第 5 行之間的內容是異常跟蹤棧信息,從打印的異常信息我們可以看出,異常從 thirdMethod 方法開始觸發,傳到 secondMethod 方法,再傳到 firstMethod 方法,最后傳到 main 方法,在 main 方法終止,這個過程就是 Java 的異常跟蹤棧。
在面向對象的編程中,大多數復雜操作都會被分解成一系列方法調用。這是因為實現更好的可重用性,將每個可重用的代碼單元定義成方法,將復雜任務逐漸分解為更易管理的小型子任務。由于一個大的業務功能需要由多個對象來共同實現,在最終編程模型中,很多對象將通過一系列方法調用來實現通信,執行任務。
所以,面向對象的應用程序運行時,經常會發生一系列方法調用,從而形成“方法調用棧”,異常的傳播則相反:只要異常沒有被完全捕獲(包括異常沒有被捕獲,或異常被處理后重新拋出了新異常),異常從發生異常的方法逐漸向外傳播,首先傳給該方法的調用者,該方法調用者再次傳給其調用者……,直至最后傳到 main 方法,如果 main 方法依然沒有處理該異常,則 JVM 會中止該程序,并打印異常的跟蹤棧信息。
很多初學者一看到上面運行結果的異常提示信息,就會驚慌失措,其實結果中的異常跟蹤棧信息非常清晰,它記錄了應用程序中執行停止的各個點。
異常跟蹤棧信息的第一行一般詳細顯示異常的類型和異常的詳細消息,接下來是所有異常的發生點,各行顯示被調用方法中執行的停止位置,并標明類、類中的方法名、與故障點對應的文件的行。一行行地往下看,跟蹤??偸亲顑炔康谋徽{用方法逐漸上傳,直到最外部業務操作的起點,通常就是程序的入口 main 方法或 Thread 類的 run 方法(多線程的情形)。
下面例子演示了多線程程序中發生異常的情形:
運行上面程序,會看到如下運行結果。
多線程異常的跟蹤棧,從發生異常的方法開始,到線程的 run 方法結束。從上面的運行結果可以看出,程序在 Thread 的 run 方法中出現了 ArithmeticException 異常,這個異常的源頭是 ThreadExcetpionTest 的 secondMethod 方法,位于 ThreadExcetpionTest.java 文件的 14 行。這個異常傳播到 Thread 類的 run 方法就會結束(如果該異常沒有得到處理,將會導致該線程中止運行)。
前面已經講過,調用 Exception 的 printStackTrace() 方法就是打印該異常的跟蹤棧信息,也就會看到上面兩個示例運行結果中的信息。當然,如果方法調用的層次很深,將會看到更加復雜的異常跟蹤棧。
printStackTrace() 方法用來追蹤異常發生的完整流程,非常適合調試程序,讓我們能夠順藤摸瓜找到問題源頭。
但是在程序發布以后應該盡量避免使用 printStackTrace(),我們應該對捕獲到的異常進行適當地處理,而不是給用戶打印一堆跟蹤棧信息,這對用戶非常不友好。