Java中的類文件結構之三:分析一個.class文件各方法的字節碼錶述語法

    接上文:Java中的類文件結構之二:分析一個.class文件的文本化閱讀

    https://blog.csdn.net/kcstrong/article/details/81233672

    在上一篇Blog中找一個示例講述瞭如何分析JVM所提供的文本形態說明的.class文件,其中各方法的字節碼部分(表述該方法的實現)並沒有說清楚,本章中希望說清楚各邏輯,但考慮到如果不講一下字節碼的詳細語法,即使分析清楚了上一篇中的示例,也是管中窺豹,只見一斑。因而在分析示例前,先說一下字節碼的詳細語法,若希望直接看示例的字節碼含義部分,可以直接看下一篇的講述。

    Java的字節碼語法相對於Java語言,類假於彙編之對於C/C++語言。一般來講,Java語言的開發者是不需要了解的,不瞭解字節碼,也能寫出Java語法,但不一定能百分之百用好Java語言。在工程上,尤其是當前互聯網行業競爭相當激烈的環境,有時候九十分是不令人滿意,一百分才行,做爲有追求的Java程序猿,我們需要熟練的掌握字節碼語法,這樣才能在系統調優,疑難問題排查方面做好。

    以下從幾個方面來講一下Java的字節碼:

    第一部分講一下字節碼都有什麼,怎麼樣分的類別

    第二部分講一下棧結構的一些基本概念,涉及虛擬機棧、線程、棧楨、操作數棧、局部變量表等

    第三部分講一下class二進制文件怎麼得到文本形態的字節符描述

    第四部分講一下由Java類編譯出的class中方法的字節碼如何工作,以示例說明

一、字節碼列表如下所示:

字節碼 助記符 指令含義
0x00 nop None
0x01 aconst_null 將null推送至棧頂
0x02 iconst_m1 將int型-1推送至棧頂
0x03 iconst_0 將int型0推送至棧頂
0x04 iconst_1 將int型1推送至棧頂
0x05 iconst_2 將int型2推送至棧頂
0x06 iconst_3 將int型3推送至棧頂
0x07 iconst_4 將int型4推送至棧頂
0x08 iconst_5 將int型5推送至棧頂
0x09 lconst_0 將long型0推送至棧頂
0x0a lconst_1 將long型1推送至棧頂
0x0b fconst_0 將float型0推送至棧頂
0x0c fconst_1 將float型1推送至棧頂
0x0d fconst_2 將float型2推送至棧頂
0x0e dconst_0 將double型0推送至棧頂
0x0f dconst_1 將double型1推送至棧頂
0x10 bipush 將單字節的常量值(-128~127)推送至棧頂
0x11 sipush 將一個短整型常量(-32768~32767)推送至棧頂
0x12 ldc 將int,float或String型常量值從常量池中推送至棧頂
0x13 ldc_w 將int,float或String型常量值從常量池中推送至棧頂(寬索引)
0x14 ldc2_w 將long或double型常量值從常量池中推送至棧頂(寬索引)
0x15 iload 將指定的int型本地變量推送至棧頂
0x16 lload 將指定的long型本地變量推送至棧頂
0x17 fload 將指定的float型本地變量推送至棧頂
0x18 dload 將指定的double型本地變量推送至棧頂
0x19 aload 將指定的引用類型本地變量推送至棧頂
0x1a iload_0 將第一個int型本地變量推送至棧頂
0x1b iload_1 將第二個int型本地變量推送至棧頂
0x1c iload_2 將第三個int型本地變量推送至棧頂
0x1d iload_3 將第四個int型本地變量推送至棧頂
0x1e lload_0 將第一個long型本地變量推送至棧頂
0x1f lload_1 將第二個long型本地變量推送至棧頂
0x20 lload_2 將第三個long型本地變量推送至棧頂
0x21 lload_3 將第四個long型本地變量推送至棧頂
0x22 fload_0 將第一個float型本地變量推送至棧頂
0x23 fload_1 將第二個float型本地變量推送至棧頂
0x24 fload_2 將第三個float型本地變量推送至棧頂
0x25 fload_3 將第四個float型本地變量推送至棧頂
0x26 dload_0 將第一個double型本地變量推送至棧頂
0x27 dload_1 將第二個double型本地變量推送至棧頂
0x28 dload_2 將第三個double型本地變量推送至棧頂
0x29 dload_3 將第四個double型本地變量推送至棧頂
0x2a aload_0 將第一個引用類型本地變量推送至棧頂
0x2b aload_1 將第二個引用類型本地變量推送至棧頂
0x2c aload_2 將第三個引用類型本地變量推送至棧頂
0x2d aload_3 將第四個引用類型本地變量推送至棧頂
0x2e iaload 將int型數組指定索引的值推送至棧頂
0x2f laload 將long型數組指定索引的值推送至棧頂
0x30 faload 將float型數組指定索引的值推送至棧頂
0x31 daload 將double型數組指定索引的值推送至棧頂
0x32 aaload 將引用類型數組指定索引的值推送至棧頂
0x33 baload 將boolean或byte型數組指定索引的值推送至棧頂
0x34 caload 將char型數組指定索引的值推送至棧頂
0x35 saload 將short型數組指定索引的值推送至棧頂
0x36 istore 將棧頂int型數值存入指定本地變量
0x37 lstore 將棧頂long型數值存入指定本地變量
0x38 fstore 將棧頂float型數值存入指定本地變量
0x39 dstore 將棧頂double型數值存入指定本地變量
0x3a astore 將棧頂引用類型數值存入指定本地變量
0x3b istore_0 將棧頂int型數值存入第一個本地變量
0x3c istore_1 將棧頂int型數值存入第二個本地變量
0x3d istore_2 將棧頂int型數值存入第三個本地變量
0x3e istore_3 將棧頂int型數值存入第四個本地變量
0x3f lstore_0 將棧頂long型數值存入第一個本地變量
0x40 lstore_1 將棧頂long型數值存入第二個本地變量
0x41 lstore_2 將棧頂long型數值存入第三個本地變量
0x42 lstore_3 將棧頂long型數值存入第四個本地變量
0x43 fstore_0 將棧頂float型數值存入第一個本地變量
0x44 fstore_1 將棧頂float型數值存入第二個本地變量
0x45 fstore_2 將棧頂float型數值存入第三個本地變量
0x46 fstore_3 將棧頂float型數值存入第四個本地變量
0x47 dstore_0 將棧頂double型數值存入第一個本地變量
0x48 dstore_1 將棧頂double型數值存入第二個本地變量
0x49 dstore_2 將棧頂double型數值存入第三個本地變量
0x4a dstore_3 將棧頂double型數值存入第四個本地變量
0x4b astore_0 將棧頂引用型數值存入第一個本地變量
0x4c astore_1 將棧頂引用型數值存入第二個本地變量
0x4d astore_2 將棧頂引用型數值存入第三個本地變量
0x4e astore_3 將棧頂引用型數值存入第四個本地變量
0x4f iastore 將棧頂int型數值存入指定數組的指定索引位置
0x50 lastore 將棧頂long型數值存入指定數組的指定索引位置
0x51 fastore 將棧頂float型數值存入指定數組的指定索引位置
0x52 dastore 將棧頂double型數值存入指定數組的指定索引位置
0x53 aastore 將棧頂引用型數值存入指定數組的指定索引位置
0x54 bastore 將棧頂boolean或byte型數值存入指定數組的指定索引位置
0x55 castore 將棧頂char型數值存入指定數組的指定索引位置
0x56 sastore 將棧頂short型數值存入指定數組的指定索引位置
0x57 pop 將棧頂數值彈出(數值不能是long或double類型的)
0x58 pop2 將棧頂的一個(對於非long或double類型)或兩個數值(對於非long或double的其他類型)彈出
0x59 dup 複製棧頂數值並將複製值壓入棧頂
0x5a dup_x1 複製棧頂數值並將兩個複製值壓入棧頂
0x5b dup_x2 複製棧頂數值並將三個(或兩個)複製值壓入棧頂
0x5c dup2 複製棧頂一個(對於long或double類型)或兩個(對於非long或double的其他類型)數值並將複製值壓入棧頂
0x5d dup2_x1 dup_x1指令的雙倍版本
0x5e dup2_x2 dup_x2指令的雙倍版本
0x5f swap 將棧頂最頂端的兩個數值互換(數值不能是long或double類型)
0x60 iadd 將棧頂兩int型數值相加並將結果壓入棧頂
0x61 ladd 將棧頂兩long型數值相加並將結果壓入棧頂
0x62 fadd 將棧頂兩float型數值相加並將結果壓入棧頂
0x63 dadd 將棧頂兩double型數值相加並將結果壓入棧頂
0x64 isub 將棧頂兩int型數值相減並將結果壓入棧頂
0x65 lsub 將棧頂兩long型數值相減並將結果壓入棧頂
0x66 fsub 將棧頂兩float型數值相減並將結果壓入棧頂
0x67 dsub 將棧頂兩double型數值相減並將結果壓入棧頂
0x68 imul 將棧頂兩int型數值相乘並將結果壓入棧頂
0x69 lmul 將棧頂兩long型數值相乘並將結果壓入棧頂
0x6a fmul 將棧頂兩float型數值相乘並將結果壓入棧頂
0x6b dmul 將棧頂兩double型數值相乘並將結果壓入棧頂
0x6c idiv 將棧頂兩int型數值相除並將結果壓入棧頂
0x6d ldiv 將棧頂兩long型數值相除並將結果壓入棧頂
0x6e fdiv 將棧頂兩float型數值相除並將結果壓入棧頂
0x6f ddiv 將棧頂兩double型數值相除並將結果壓入棧頂
0x70 irem 將棧頂兩int型數值作取模運算並將結果壓入棧頂
0x71 lrem 將棧頂兩long型數值作取模運算並將結果壓入棧頂
0x72 frem 將棧頂兩float型數值作取模運算並將結果壓入棧頂
0x73 drem 將棧頂兩double型數值作取模運算並將結果壓入棧頂
0x74 ineg 將棧頂int型數值取負並將結果壓入棧頂
0x75 lneg 將棧頂long型數值取負並將結果壓入棧頂
0x76 fneg 將棧頂float型數值取負並將結果壓入棧頂
0x77 dneg 將棧頂double型數值取負並將結果壓入棧頂
0x78 ishl 將int型數值左移指定位數並將結果壓入棧頂
0x79 lshl 將long型數值左移指定位數並將結果壓入棧頂
0x7a ishr 將int型數值右(帶符號)移指定位數並將結果壓入棧頂
0x7b lshr 將long型數值右(帶符號)移指定位數並將結果壓入棧頂
0x7c iushr 將int型數值右(無符號)移指定位數並將結果壓入棧頂
0x7d lushr 將long型數值右(無符號)移指定位數並將結果壓入棧頂
0x7e iand 將棧頂兩int型數值"按位與"並將結果壓入棧頂
0x7f land 將棧頂兩long型數值"按位與"並將結果壓入棧頂
0x80 ior 將棧頂兩int型數值"按位或"並將結果壓入棧頂
0x81 lor 將棧頂兩long型數值"按位或"並將結果壓入棧頂
0x82 ixor 將棧頂兩int型數值"按位異或"並將結果壓入棧頂
0x83 lxor 將棧頂兩long型數值"按位異或"並將結果壓入棧頂
0x84 iinc 將指定int型變量增加指定值(如i++, i--, i+=2等)
0x85 i2l 將棧頂int型數值強制轉換爲long型數值並將結果壓入棧頂
0x86 i2f 將棧頂int型數值強制轉換爲float型數值並將結果壓入棧頂
0x87 i2d 將棧頂int型數值強制轉換爲double型數值並將結果壓入棧頂
0x88 l2i 將棧頂long型數值強制轉換爲int型數值並將結果壓入棧頂
0x89 l2f 將棧頂long型數值強制轉換爲float型數值並將結果壓入棧頂
0x8a l2d 將棧頂long型數值強制轉換爲double型數值並將結果壓入棧頂
0x8b f2i 將棧頂float型數值強制轉換爲int型數值並將結果壓入棧頂
0x8c f2l 將棧頂float型數值強制轉換爲long型數值並將結果壓入棧頂
0x8d f2d 將棧頂float型數值強制轉換爲double型數值並將結果壓入棧頂
0x8e d2i 將棧頂double型數值強制轉換爲int型數值並將結果壓入棧頂
0x8f d2l 將棧頂double型數值強制轉換爲long型數值並將結果壓入棧頂
0x90 d2f 將棧頂double型數值強制轉換爲float型數值並將結果壓入棧頂
0x91 i2b 將棧頂int型數值強制轉換爲byte型數值並將結果壓入棧頂
0x92 i2c 將棧頂int型數值強制轉換爲char型數值並將結果壓入棧頂
0x93 i2s 將棧頂int型數值強制轉換爲short型數值並將結果壓入棧頂
0x94 lcmp 比較棧頂兩long型數值大小, 並將結果(1, 0或-1)壓入棧頂
0x95 fcmpl 比較棧頂兩float型數值大小, 並將結果(1, 0或-1)壓入棧頂; 當其中一個數值爲NaN時, 將-1壓入棧頂
0x96 fcmpg 比較棧頂兩float型數值大小, 並將結果(1, 0或-1)壓入棧頂; 當其中一個數值爲NaN時, 將1壓入棧頂
0x97 dcmpl 比較棧頂兩double型數值大小, 並將結果(1, 0或-1)壓入棧頂; 當其中一個數值爲NaN時, 將-1壓入棧頂
0x98 dcmpg 比較棧頂兩double型數值大小, 並將結果(1, 0或-1)壓入棧頂; 當其中一個數值爲NaN時, 將1壓入棧頂
0x99 ifeq 當棧頂int型數值等於0時跳轉
0x9a ifne 當棧頂int型數值不等於0時跳轉
0x9b iflt 當棧頂int型數值小於0時跳轉
0x9c ifge 當棧頂int型數值大於等於0時跳轉
0x9d ifgt 當棧頂int型數值大於0時跳轉
0x9e ifle 當棧頂int型數值小於等於0時跳轉
0x9f if_icmpeq 比較棧頂兩int型數值大小, 當結果等於0時跳轉
0xa0 if_icmpne 比較棧頂兩int型數值大小, 當結果不等於0時跳轉
0xa1 if_icmplt 比較棧頂兩int型數值大小, 當結果小於0時跳轉
0xa2 if_icmpge 比較棧頂兩int型數值大小, 當結果大於等於0時跳轉
0xa3 if_icmpgt 比較棧頂兩int型數值大小, 當結果大於0時跳轉
0xa4 if_icmple 比較棧頂兩int型數值大小, 當結果小於等於0時跳轉
0xa5 if_acmpeq 比較棧頂兩引用型數值, 當結果相等時跳轉
0xa6 if_acmpne 比較棧頂兩引用型數值, 當結果不相等時跳轉
0xa7 goto 無條件跳轉
0xa8 jsr 跳轉至指定的16位offset位置, 並將jsr的下一條指令地址壓入棧頂
0xa9 ret 返回至本地變量指定的index的指令位置(一般與jsr或jsr_w聯合使用)
0xaa tableswitch 用於switch條件跳轉, case值連續(可變長度指令)
0xab lookupswitch 用於switch條件跳轉, case值不連續(可變長度指令)
0xac ireturn 從當前方法返回int
0xad lreturn 從當前方法返回long
0xae freturn 從當前方法返回float
0xaf dreturn 從當前方法返回double
0xb0 areturn 從當前方法返回對象引用
0xb1 return 從當前方法返回void
0xb2 getstatic 獲取指定類的靜態域, 並將其壓入棧頂
0xb3 putstatic 爲指定類的靜態域賦值
0xb4 getfield 獲取指定類的實例域, 並將其壓入棧頂
0xb5 putfield 爲指定類的實例域賦值
0xb6 invokevirtual 調用實例方法
0xb7 invokespecial 調用超類構建方法, 實例初始化方法, 私有方法
0xb8 invokestatic 調用靜態方法
0xb9 invokeinterface 調用接口方法
0xba invokedynamic 調用動態方法
0xbb new 創建一個對象, 並將其引用引用值壓入棧頂
0xbc newarray 創建一個指定的原始類型(如int, float, char等)的數組, 並將其引用值壓入棧頂
0xbd anewarray 創建一個引用型(如類, 接口, 數組)的數組, 並將其引用值壓入棧頂
0xbe arraylength 獲取數組的長度值並壓入棧頂
0xbf athrow 將棧頂的異常拋出
0xc0 checkcast 檢驗類型轉換, 檢驗未通過將拋出 ClassCastException
0xc1 instanceof 檢驗對象是否是指定類的實際, 如果是將1壓入棧頂, 否則將0壓入棧頂
0xc2 monitorenter 獲得對象的鎖, 用於同步方法或同步塊
0xc3 monitorexit 釋放對象的鎖, 用於同步方法或同步塊
0xc4 wide 擴展本地變量的寬度
0xc5 multianewarray 創建指定類型和指定維度的多維數組(執行該指令時, 操作棧中必須包含各維度的長度值), 並將其引用壓入棧頂
0xc6 ifnull 爲null時跳轉
0xc7 ifnonnull 不爲null時跳轉
0xc8 goto_w 無條件跳轉(寬索引)
0xc9 jsr_w 跳轉至指定的32位offset位置, 並將jsr_w的下一條指令地址壓入棧頂

    字節碼操作符後面會有一個或0個操作數,操作數可能的數據類型包括:l代表long,s代表short、i代表int、b代表byte、c代表char、f代表float、d代表double、a代表reference。以下提到了幾個存儲區:

    局部變量表:在方法解析爲字節碼時就已定好的,存儲所用到的局部變量的地方

    操作數棧:一個先進後出的棧,用於執行字節碼操作,入棧的可能是操作符,可能是操作數

    常量:位於常量池中

    便與記憶,可以將操作符分爲以下幾類:(源於《深入理解Java虛擬機》)

    1.加載與存儲指令

    將一個數值從局部變量表中加載至操作數棧,如:iload(iload_0 == iload 0)

    將一個數值從操作數棧存入局部變量表,如:istore(istore_0 == istore 0)

    將一個常量加載到操作數棧,如:bipush

    擴充局部變量表的訪問索引,如:wide

    2.運算指令

    加減乘除,例:iadd、fdiv等

    位運算,例:ior、ixor等

    自增指令:iinc,注意,該運算符操作的是局部變量表,與棧無關

    比較指令,例:lcmp

    3.類型轉換指令,例:i2b(整型int轉byte)、f2i(float轉int)

    4.對象的創建與該問指令

    創建類實例:new,注意:先創建的是類實例,然後纔是調用構造方法,也就是說new A(),不止一條操作符

    創建數組,例:newarray

    訪問類字段或實例字段:getfield、putfield、getstatic、putstatic

    檢查類型:instanceof、checkcast

    其他數組操作

    5.管理操作數棧:pop、pop2、dup等、swap

    6.控制跳轉:if有關的(ifeq等)、tableswitch、lookupswitch、goto等

    7.方法調用:

    invokevirual:調用對象的實例方法(除實現的接口方法外)

    invokespecial:調用構造方法、私有方法、父類方法

    invokestatic:調用類方法

    invokeinterface:調用接口方法

    invokedynamic:用戶設定的引導方法(一些動態語言特性相關的支持,在JDK1.8引入)

    8.異常處理指令:athrow

    9.同步指令:monitorenter、monitorexit

二、相關的幾個概念詳述:

    1.棧結構與寄存器結構:目前的PC指令集架構的兩種方式,各有優缺點,棧結構平臺相關性低,可以跨平臺運行於不同的硬件系統上,在x86和arm系列可能同樣處理。寄存器結構運行更快,運行相同邏輯所需要的操作數也要更少,比如Android虛擬機對資源相當敏感,就是寄存器的指令集結構。

    2.虛擬機棧與線程、棧幀:Java的方法都是運行於線程中的,或者是主線程,或者是工作線程,每一個方法都是一個棧幀。多個方法(棧幀)組成了一個線程內存塊、每個線程的內存放於一個虛擬機棧中,也就是我們常說的棧區(最大的對比區別是堆區),當前線程正在處理的就是位於最上方的棧幀,如下圖所示(圖片來源於:《深入理解Java虛擬機》):

    3.操作數棧與局部變量表:均在方法編譯爲字節碼的同時,就定義好了兩者的大小,分別用:max_stack代表操作棧的最大深度,max_locals代表局部變量表的大小,注意,max_locals的單位爲slot。JVM是基於棧的指令結構,各方法的運行就是通過操作棧與局部變量表配合實現的邏輯,在第一部分中可以看到從操作棧到局部變量表雙向的操作。

三、看一下javap是如何由二進制文件得到文本描述的字節碼錶述的,看一個例子:

package com.demo.kcsdemo.java;

import java.util.Random;

public class Temp5Test {

    int index;

    public int incc(int incval) {
        Random random = new Random();
        int ri = random.nextInt();
        index = index + incval;
        return index;
    }
}

    編譯該類:javac com/demo/kcsdemo/java/Temp5Test.java,二進制輸出如下所示:

    生成字節符描述:javap -verbose Temp5Test

Classfile /Users/wangguoqiang/AndroidStudioProjects/KCSDemo/app/src/main/java/com/demo/kcsdemo/java/Temp5Test.class
  Last modified 2018-7-31; size 389 bytes
  MD5 checksum 03fc35b2dc3f1aa80072f1a37baedd58
  Compiled from "Temp5Test.java"
public class com.demo.kcsdemo.java.Temp5Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#18         // java/lang/Object."<init>":()V
   #2 = Class              #19            // java/util/Random
   #3 = Methodref          #2.#18         // java/util/Random."<init>":()V
   #4 = Methodref          #2.#20         // java/util/Random.nextInt:()I
   #5 = Fieldref           #6.#21         // com/demo/kcsdemo/java/Temp5Test.index:I
   #6 = Class              #22            // com/demo/kcsdemo/java/Temp5Test
   #7 = Class              #23            // java/lang/Object
   #8 = Utf8               index
   #9 = Utf8               I
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               incc
  #15 = Utf8               (I)I
  #16 = Utf8               SourceFile
  #17 = Utf8               Temp5Test.java
  #18 = NameAndType        #10:#11        // "<init>":()V
  #19 = Utf8               java/util/Random
  #20 = NameAndType        #24:#25        // nextInt:()I
  #21 = NameAndType        #8:#9          // index:I
  #22 = Utf8               com/demo/kcsdemo/java/Temp5Test
  #23 = Utf8               java/lang/Object
  #24 = Utf8               nextInt
  #25 = Utf8               ()I
{
  int index;
    descriptor: I
    flags:

  public com.demo.kcsdemo.java.Temp5Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 5: 0

  public int incc(int);
    descriptor: (I)I
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=4, args_size=2
         0: new           #2                  // class java/util/Random
         3: dup
         4: invokespecial #3                  // Method java/util/Random."<init>":()V
         7: astore_2
         8: aload_2
         9: invokevirtual #4                  // Method java/util/Random.nextInt:()I
        12: istore_3
        13: aload_0
        14: aload_0
        15: getfield      #5                  // Field index:I
        18: iload_1
        19: iadd
        20: putfield      #5                  // Field index:I
        23: aload_0
        24: getfield      #5                  // Field index:I
        27: ireturn
      LineNumberTable:
        line 10: 0
        line 11: 8
        line 12: 13
        line 13: 23
}
SourceFile: "Temp5Test.java"

    直接看方法incc的部分:

  public int incc(int);
    descriptor: (I)I
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=4, args_size=2
         0: new           #2                  // class java/util/Random
         3: dup
         4: invokespecial #3                  // Method java/util/Random."<init>":()V
         7: astore_2
         8: aload_2
         9: invokevirtual #4                  // Method java/util/Random.nextInt:()I
        12: istore_3
        13: aload_0
        14: aload_0
        15: getfield      #5                  // Field index:I
        18: iload_1
        19: iadd
        20: putfield      #5                  // Field index:I
        23: aload_0
        24: getfield      #5                  // Field index:I
        27: ireturn
      LineNumberTable:
        line 10: 0
        line 11: 8
        line 12: 13
        line 13: 23

    stack=3表示最大操作數棧深度爲3,locals=4表示局部變,局部變量量表中有4個值,args_size=2表示有兩個參數,爲什麼是兩個參數呢?在實例方法(歸屬於對象的方法)中,第一個參數默認爲this,即操作該方法的當前對象,然後按先後順序排列其他的參數,因此incc方法有兩個參數,局部變量表中第0位爲this,然後爲方法的參數表,由先到後,在incc中,第1位爲參數incval。

    該方法的二進制碼位於如下圖所示,在兩個紅色標識之間由00000120h0d~00000170h0a:

    

    二進制的分析方法在第一篇:Java中的類文件結構之一:如何分析一個.class文件的二進制碼內容

    https://blog.csdn.net/kcstrong/article/details/79460262

    之中有詳細的描述,不明白的可以看一下。現在簡單的再提練一下二進制位與字節碼的對應關係:

    0x0001:public方法,字節碼的如下部分

flags: ACC_PUBLIC

    0x000E000F:對應於常量池中的#14和#15,爲該方法的名稱和描述

  #14 = Utf8               incc
  #15 = Utf8               (I)I

    字節碼的如下部分:

  public int incc(int);
    descriptor: (I)I

    0x0001:包含一個屬性表,0x000C:對應於常量池中的#12,字節碼部分:

 Code:

    0x00000040:屬性表除名稱和長度外還有0x40個字節

    0x0003:max_stack爲3,0x0004:max_local爲4,對應於字節碼錶述爲:

     stack=3, locals=4, 

    0x0000001C:字節碼長1*16+12=28位,後面的28位即爲字節碼:

    0xBB00...05AC,再看一下字節碼錶述,正好28位(最左側一排即爲操作符的位偏移量):

         0: new           #2                  // class java/util/Random
         3: dup
         4: invokespecial #3                  // Method java/util/Random."<init>":()V
         7: astore_2
         8: aload_2
         9: invokevirtual #4                  // Method java/util/Random.nextInt:()I
        12: istore_3
        13: aload_0
        14: aload_0
        15: getfield      #5                  // Field index:I
        18: iload_1
        19: iadd
        20: putfield      #5                  // Field index:I
        23: aload_0
        24: getfield      #5                  // Field index:I
        27: ireturn

    從裏面找個例子說明下吧,比如,第15條操作符

        15: getfield      #5                  // Field index:I

    從0xBB開始,第15位偏移的二進字爲0xB4,查找本篇一開始的操作符表,可知,getfield在表中的位置正是b4位

    

    該操作符後面跟隨着操作數,佔兩位(16、17),因爲下一條操作是從18開始的,因而操作數爲0x0005,正是常量池中的#5常量。

    字節碼後面的二進制對應直接從屬性表一一查找,即可,不再展開了,下圖爲屬性表的各字段含義:

    

四、看一下上述例子中的字節碼的執行過程是什麼:

         0: new           #2                  // class java/util/Random
         3: dup
         4: invokespecial #3                  // Method java/util/Random."<init>":()V
         7: astore_2
         8: aload_2
         9: invokevirtual #4                  // Method java/util/Random.nextInt:()I
        12: istore_3
        13: aload_0
        14: aload_0
        15: getfield      #5                  // Field index:I
        18: iload_1
        19: iadd
        20: putfield      #5                  // Field index:I
        23: aload_0
        24: getfield      #5                  // Field index:I
        27: ireturn

    第0行new執行前的棧爲空,局部變量表爲如下所示:

索引 變量 類型
0 this 引用a
1 incval 整型i
2    
3    


       

 

 

 

 

    第0~4條操作符爲創建Random對象,通過new創建對象的過程均爲該寫法,調用了Random的類實例及構造方法,初始化爲random變量,執行完畢後局部變量表不變,操作數棧如下:

 
 
random

    第7條操作,將操作數棧頂的變量存入局部變量表中,操作完畢後的局部變量表如下所示,棧爲空

索引 變量 類型
0 this 引用a
1 incval 整型i
2 random 引用a

       3

   

 

 

 

 

 

 

    第8條操作將局部變量表中的第2條變量入棧,執行完畢後操作數棧如下:

 
 
random

    第9條操作調用random的nextInt()方法,執行完畢後操作數棧如下所示:

 
 
ri

    第12條將棧頂元素入局部變量表第3的位置:

索引 變量 類型
0 this 引用a
1 incval 整型i
2 random 引用a

       3

ri 整型i

 

 

 

 

 

 

    第13、14條執行完畢後,棧變爲:

 
this
this

    第15條調用this的getfield,得到當前對象的相關域index並放入棧頂,棧變爲:

 
index
this

    第18條執行從局部變量表載入incval,棧變爲:

incval
index
this

    第19條將棧頂元素出頂相加再入棧,棧變爲:

 
index+incval的值
this

    第20條將值放入index域中,棧中元素均已出棧,棧空

    第23、24條爲讀入this,然後再得到index域並放入棧頂,執行完畢後棧爲:

 
 
index

    第27條返回該值

 

本文爲作者原創作品,寫作不易,轉載請註明出處

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