解讀Java報錯輸出的信息究竟是什么
Java報錯輸出的信息究竟是什么?
本篇會帶大家了解一下java運行時報錯輸出的信息內(nèi)容,簡單學習一下虛擬機內(nèi)存中Java虛擬機棧的工作方式以及棧幀中所存儲的信息內(nèi)容
異常信息
當你的程序運行報錯時,你是否會好奇打印出來的那一大坨紅色的究竟是什么?
首先,報錯的第一行是異常的主要描述信息,那么剩下的基本都是當前線程的棧幀信息
比如上圖對于異常的描述是
Access denied for user 'root'@'localhost' (using password: YES)
我們的數(shù)據(jù)庫訪問被拒絕了,原因就是我把密碼輸入錯誤了
那么異常描述下面的信息就是棧幀信息了
什么是棧幀呢,棧幀中又包含了哪些信息?
虛擬機棧
在我們的Java虛擬機中,有一個組成部分叫做運行時數(shù)據(jù)區(qū),它是程序執(zhí)行過程中用于存儲數(shù)據(jù)的內(nèi)存區(qū)域,這個區(qū)域用于存儲程序運行過程中的各種數(shù)據(jù),包括對象實例、類信息、局部變量、方法調(diào)用信息等
在運行時數(shù)據(jù)區(qū)中,又有一部分是虛擬機棧,每個線程都有一個獨立的虛擬機棧,當方法被調(diào)用時,會在棧中創(chuàng)建一個新的棧幀(Stack Frame),當方法執(zhí)行完畢后,棧幀會被彈出,控制權(quán)回到調(diào)用方,所以虛擬機棧中進進出出的就是程序中的方法的棧幀
那么虛擬機棧是怎么運行的呢
這里以一段代碼的運行為例子:
public class Stark { public static void main(String[] args) { FunctionA(); } public static void FunctionA(){ System.out.println("運行了A"); FunctionB(); } public static void FunctionB(){ System.out.println("運行了B"); } }
當我們運行這段程序時,第一個執(zhí)行的是Main方法,在Main方法中又調(diào)用執(zhí)行了A方法,在A方法中又執(zhí)行了B方法,那么在虛擬機棧中,要執(zhí)行的方法的棧幀會進入虛擬機棧中,待方法完全執(zhí)行完畢后,他的棧幀才會被彈出:
此時,三個方法都還沒有完全結(jié)束,當B方法被調(diào)用,輸出"運行了B"后,B方法的棧幀才會彈出虛擬機棧,以此類推:
這就是虛擬機棧的運行過程,我們也可以在IDEA中使用斷點調(diào)試查看到線程的虛擬機棧,依然使用上面的代碼,在B方法上打斷點,點擊調(diào)試運行:
在左下角就可以看到目前存在的線程中的棧中有哪些棧幀:
此時正處于B方法中,也就是上圖中A方法剛剛調(diào)用的B方法,B方法還未結(jié)束時,棧中有三個方法的棧幀信息
文章開頭提到,報錯時會輸出當前線程全部的棧幀信息,我們可以試一下
在上述代碼的B方法中添加一句拋出異常的代碼:
public class Stark { public static void main(String[] args) { FunctionA(); } public static void FunctionA(){ System.out.println("運行了A"); FunctionB(); } public static void FunctionB(){ System.out.println("運行了B"); throw new RuntimeException("出錯"); } }
執(zhí)行代碼,你可以在控制臺看到打印出了如下內(nèi)容:
在報錯信息的第一行顯示,我們在線程mian中發(fā)生了運行時異常,異常信息:“出錯”
接著,很顯然下面的部分就是棧幀信息,他的打印順序就是當前虛擬機棧中的全部棧幀依次打印
什么是棧幀
相信看到這里,你會發(fā)出疑問,什么是棧幀呢?虛擬機棧中放入的棧幀到底是個什么東西呢?
棧幀是虛擬機棧的基本存儲單元,主要是由三部分組成:
- 局部變量表:
用于存放方法的參數(shù)和局部變量。這些變量在方法執(zhí)行過程中會被頻繁訪問,因此將它們存儲在棧幀中可以提高訪問速度。局部變量表中的變量在方法調(diào)用時初始化,并在方法執(zhí)行完畢后銷毀 - 操作數(shù)棧:
用于保存計算過程中產(chǎn)生的中間結(jié)果和作為計算單元的操作數(shù)。操作數(shù)棧是一個后進先出(LIFO)的數(shù)據(jù)結(jié)構(gòu),與局部變量表一起支持方法的執(zhí)行。 - 幀數(shù)據(jù):
主要包含動態(tài)鏈接、方法出口、異常表等信息
局部變量表
我們編譯StarkTest類并查看他的字節(jié)碼文件:
public class StarkTest { public static void main(String[] args) { int i = 0; int j = i + 1; } }
打開方法,找到Mian方法下的LocalVariableTable就是局部變量表,表中會把方法中的參數(shù)和局部變量存放起來,索引從0開始,由于我們的main方法傳入了參數(shù)args,那么表中下標為0的槽中放了args,方法中我們又聲明了i,和j,那么他們會依次保存到槽中
其實局部變量表就是一個數(shù)組,每一個位置被稱為一個槽,除了long類型和double類型的變量要占兩個槽,其余的都只占一個槽,需要注意的是如果該方法不是靜態(tài)方法而是示例方法,該方法運行時局部變量表的第一個槽會存放調(diào)用該方法的實例對象,也就是this
局部變量表中存放的變量如果在其生效范圍內(nèi)不會在被使用,那么之后的變量或是參數(shù)就可以覆蓋他的槽,以此節(jié)省空間
操作數(shù)棧
他就是一塊用來存放運行時中間數(shù)據(jù)的區(qū)域,比如上邊代碼中int i = 0;int j = i + 1;這兩行代碼執(zhí)行的過程,先把0放入操作數(shù)棧中,再將操作數(shù)棧中的數(shù)賦值給變量i,然后把i的值放入操作數(shù)棧中,再把1也放入操作數(shù)棧中,讓操作數(shù)棧中的兩個數(shù)相加,再賦值給變量j:
幀數(shù)據(jù)
幀數(shù)據(jù)中主要是動態(tài)鏈接,方法出口
- 動態(tài)鏈接:
用于維護方法調(diào)用時的符號引用轉(zhuǎn)化為直接引用的信息。在Java中,每個類都有一個常量池,用于存儲符號引用。當方法被調(diào)用時,JVM會通過動態(tài)鏈接將符號引用轉(zhuǎn)化為直接引用,以便在方法執(zhí)行過程中訪問其他類或方法。 - 方法出口:
記錄方法正常退出或異常退出時的處理信息。當方法執(zhí)行完畢后,JVM會根據(jù)方法出口的信息將控制權(quán)返回給調(diào)用者。如果方法在執(zhí)行過程中拋出異常,JVM也會根據(jù)方法出口的信息來處理異常
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
使用JVMTI實現(xiàn)SpringBoot的jar加密,防止反編譯
這篇文章主要介紹了使用JVMTI實現(xiàn)SpringBoot的jar加密,防止反編譯問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08Spring MVC學習教程之RequestMappingHandlerAdapter詳解
這篇文章主要給大家介紹了關(guān)于Spring MVC學習教程之RequestMappingHandlerAdapter的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧2018-11-11Spring-boot原理及spring-boot-starter實例和代碼
spring-boot的starter是一個通過maven完成自包含并通過annotation配置使得可被spring上下文發(fā)現(xiàn)并實例化的一個可插拔的組件或服務。這篇文章主要介紹了Spring-boot原理及spring-boot-starter實例和代碼 ,需要的朋友可以參考下2019-06-06Mybatis入門教程(四)之mybatis動態(tài)sql
這篇文章主要介紹了Mybatis入門教程(四)之mybatis動態(tài)sql的相關(guān)資料,涉及到動態(tài)sql及動態(tài)sql的作用知識,本文介紹的非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-09-09