JVM的字節碼指令(一步步讀懂.class字節碼文件的操作指令)

剛上大一那會兒就是隨便一個編譯器寫.java後綴文件,然後通過命令行JavaC編譯那個.java後綴的文件生成.class文件,然後直接java XXXX.class文件就可以運行自己的Java程序。

所以知道,Java 程序執行分兩個階段,編譯階段和運行階段:

JavaC :這個命令就會啓動Java的編譯器去對Java後綴文件進行編譯,生成字節碼,也就是.class文件,這個文件是十六進制格式的,裏面的內容有魔數,常量池,訪問標誌,類索引,字段表,方法表,屬性表還有一堆操作指令。

Java:這個命令會啓動JVM虛擬機去執行字節碼,Java得益於跨平臺,一次編寫處處運行的優勢就是基於JVM,只要按照規範寫出編譯器可以編譯的代碼(當然也可以自定義編譯器進行解析)並且編譯出字節碼文件,JVM都能在任何平臺上運行你寫出來的代碼。

下面看例子:

這是我寫好的一個例子,不要管邏輯,重點看字節碼操作指令。

在這裏插入圖片描述

下一步,Javac編譯。

在這裏插入圖片描述
在這裏插入圖片描述

輸入javap -verbose XXX.class查看查看字節碼文件。

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

這個就是剛纔寫的那個小小的demo裏面的全部字節碼,雖然很難看,但是這已經是經過verbose工具優化後的了,如果是直接打開.class文件,會看到 cafe babe 等魔數,還有一堆十六進制的數,更加難看懂。畢竟這裏的語法和格式看起來都相當於另外一門語言。

對照着下面的這個表看,應該就能一行行讀懂了:

加載和存儲的操作指令:

加載變量:

iload : 加載一個int類型的局部變量到棧幀的操作數棧中。
lload : 加載一個long類型的局部變量到棧幀的操作數棧中。
fload , dload : 就是加載一個 float 和 double類型的局部變量到棧幀的操作數棧中。
aload : 引用類型。(上面的都是基本類型,a 開頭的load是引用類型,也就是對象或者數組的引用加載到操作數棧)

加載常量:

bipush : byte類型常量進棧
sipush : 當int類型常量取值-32768~32767時使用該指令進棧。(小一點)
Idc : 當int類型常量取值-2147483648~2147483647時使用該指令進棧。(大一點)
iconst_1 : int 類型常量值1進棧。
aconst : 引用類型常量進棧。
…當然還有很多很多,這裏每列出來的就靠上網查了

存儲指令(從操作數棧執行完存儲到局部變量表)

istore : int 類型的。
lstore : long 類型的。
fstore dstore astore : 打字好累,反正都是一樣的尿性。就是區分不同類型而已。

運算操作指令:

iadd : int類型的操作數值相加運算
isub : 相減運算
imul : 相乘運算
idiv : 相除運算
irem : int類型的操作數棧的兩個值取餘
ineg : int 類型取反
等等…float 和 double 的也是一個道理

類型轉換:

我們經常把int轉double,把char轉string,就是類型轉換,包括對象的上轉型和下轉型等。JVM中很好理解,就是等待轉換的類型縮寫+2+轉換後的類型。

i2l : int 類型轉long類型
i2c : int 轉char
f2d:float轉double等…

對象創建指令:

new

訪問類對象中的字段:

getfield : 獲取一個操作數的值。
putfield :接受一個操作數,這個操作數引用的是運行時常量池裏的一個字段,在這裏這個字段是simpleField。賦給這個字段的值,以及包含這個字段的對象引用,在執行這條指令的時候,都 會從操作數棧頂上pop出來。方法執行到最後的時候也會putfield一次,把操作完的操作數棧的數值pop出棧。
baload : 把數組的元素加載到操作數棧,aload我們知道是引用類型入棧,前面再加一個基本數據類型的縮寫就是具體引用類型的某個元素。caload (char) iaload ( int ) laload ( long ) daload ( double ) aaload ( 引用類型中的引用類型,比如Java實現二叉樹中的對象包對象 )
astore : 把操作數棧值存儲到數組元素位置。

調用方法指令:

invokeinterface : 接口方法,運行時搜索實現了該接口方法的對象,並找到對應方法執行。
invokespecial : 特殊處理的實例化方法,初始化方法,私有方法,父類方法。
invokestatic : 調用了類方法,也就是static方法。main方法就是一個invokestatic調用。

返回指令:

return : 也代表着方法的執行結束,同時這個方法的棧幀從棧(方法運行描述模型)中彈出。

異常指令:

athrow
我們代碼中編寫的try catch其實是通過控制轉移指令和athrow異常指令實現的。在字節碼中是沒有try catch語句塊的字節碼的,不信自己寫個try catch看一下就知道了。try catch代碼塊會在字節碼文件中有一個Exception Table 異常表,異常表有from / to / target / type 這幾個字段 ,from to 代表了try中的檢測語句塊的範圍行數,target意思是假如補貨到指定的type類型異常就跳到指定的行數執行處理,type就是定義的可能拋出的異常類型。
這裏借用傳智播客大佬的一張截圖
在這裏插入圖片描述

控制轉移指令:

goto #XX行
有的人矇蔽了,不是說Java沒有goto嗎?這裏的goto是字節碼文件裏面的,Java裏面的goto保留字不同,這裏是打破代碼的順序執行機制,強制它跳過幾行或者重新執行幾行,這就是控制轉移指令。
ifeq : 假如相等,則 執行到# XX行。
iflt : 假如小於(if less than )則。。。
ifgt : 假如大於,(greater than)則。。。。。。

這裏我只是大概羅列了一下,全部的指令我也是隻記住一些基本的常用的,很多我也是要靠查資料,但是能看懂就行,不去背誦也沒關係,畢竟對業務開發沒什麼用哈哈哈哈哈。

看完這個翻譯後我們再看回去字節碼,是不是忽然明白它在描述做着什麼了呢?

在這裏插入圖片描述

疫情嚴重,上班推遲了,趁着這幾天空閒,總結出更多的博客,一是自己溫習,二是給大家分享,謝謝大家,說的不對的地方請指正,鞠躬!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章