深入理解java位元組碼
Javap 反編譯class檔案 –verbose 顯示冗餘資訊
(1)魔數:所有的class位元組碼檔案的4個位元組都是魔數,魔數固定值:0xCAFEBABE
(2)版本:魔數之後4個位元組是版本資訊,前兩個位元組minor version次版本號例如0,後兩個位元組是主機板號major version例如52表示1.8.0。
(3)常量池:主版本後就是常量池入口。常量池的長度不是固定的。Java類中定義的很多資訊都是由常量池來維護和描述的。可以將常量池看做是Class檔案的資源倉庫,例如說java類中定義的方法與變數資訊。常量池中主要儲存兩類常量:字面量和符號引用。
①字面量:文字字串,final常量。
②符號引用:類和介面的全侷限定名。欄位名稱和描述符,方法的名稱和描述符。
Java類所對應的常量池主要由常量池數量與常量池陣列(也稱常量表)這兩部分共同構成。(這麼做可以最大壓縮儲存檔案體積)。常量池數量緊跟在主版本號後面,佔用2個位元組。常量池陣列僅跟在常量池數量之後。常量池陣列不同與一般陣列,不同元素型別,結構都不相同。每一個元素的第一個陣列都是u1型別,是一個標誌位,佔據1個位元組,JVM在解析常量池時,會根據u1型別來獲取元素的具體型別。值得注意的是,常量池陣列中元素的個數=常量池-1(其中0暫時不使用)。目的是滿足某些常量池索引值的資料在特定情況下所需表達[不引用任何常量池]的含義。索引0是一個保留常量,不位於常量陣列中,對應null值,所以索引從1開始。
(4)在jvm規範中,每個變數/欄位都有描述資訊,描述資訊主要作用是描述欄位的資料型別,方法的引數列表(包括數量、型別、順序)與返回值。根據描述符規則,基本資料型別和代表無返回值的void型別都用一個大寫字元來表示,物件型別則使用字串L加物件的許可權定名來表示。為了壓縮位元組碼的體積,對於基本資料型別,jvm都只使用一個大寫字母來表示,如下所示:B –byte, C –char, D –double, F—float, I—int, J –long S – short, Z – Boolean, V—void, L—物件型別。例如Ljava/lang/String
對於陣列型別來講,每一個維度使用一個前置的[來表示,如int[]被標記為[I,String[][]被標記為[[Ljava.lang.String 注意:這個是給機器看的
用描述符描述方法時,按照先引數列表,後返回值的順序來描述。引數列表按照引數的嚴格順序放在一組()之內。例如String getRealnameByIdAndNickname(int id, String name)的描述為:(I,Ljava/lang/string;) Ljava/lang/String;
Constant pool: #1 = Methodref #4.#20 // java/lang/Object."<init>":()V #2 = Fieldref #3.#21 // com/lesson/model/MyTest.a:I #3 = Class #22 // com/lesson/model/MyTest #4 = Class #23 // java/lang/Object #5 = Utf8 a #6 = Utf8 I #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lcom/lesson/model/MyTest; #14 = Utf8 getA #15 = Utf8 ()I #16 = Utf8 setA #17 = Utf8 (I)V #18 = Utf8 SourceFile #19 = Utf8 MyTest.java #20 = NameAndType #7:#8 // "<init>":()V #21 = NameAndType #5:#6 // a:I #22 = Utf8 com/lesson/model/MyTest #23 = Utf8 java/lang/Object { public com.lesson.model.MyTest(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: iconst_1 6: putfield #2 // Field a:I 9: return LineNumberTable: line 3: 0 line 5: 4 LocalVariableTable: Start Length Slot Name Signature 0 10 0 this Lcom/lesson/model/MyTest;
public int getA(); descriptor: ()I flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #2 // Field a:I 4: ireturn LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/lesson/model/MyTest;
public void setA(int); descriptor: (I)V flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=2 0: aload_0 1: iload_1 2: putfield #2 // Field a:I 5: return LineNumberTable: line 12: 0 line 13: 5 LocalVariableTable: Start Length Slot Name Signature 0 6 0 this Lcom/lesson/model/MyTest; 0 6 1 a I } |
(1) 00 36 表示次版本號例如0,主版本號1.8.0
(2) 00 18 (索引0,陣列長度)常量池索引0位置為保留, 16進位制18即24,也就是陣列長度,但是0是保留常量,所以準確來說23.
(3)0A tag值10 對應constant_methodref_info,佔據4個位元組
前兩個表示指向宣告欄位的類或者介面描述符constant_class_info的索引(00 04--即4)
後兩個表示名稱及型別描述符constant_nameandtype_info的索引項(00 14即20)
#1 = Methodref #4.#20 // java/lang/Object."<init>":()V
(4)09 tag值9 對應constant_fieldref_info,佔據4個位元組
前兩個表示欄位的型別或者介面描述符constant_class_info的索引項(00 03即3)
後兩個表示欄位描述符constant_nameandtype_info的索引項(00 15即21)
#2 = Fieldref #3.#21 // com/lesson/model/MyTest.a:I
#3-class表示當前類
#21-對應兩個部分:#5#6; #5--a(欄位名稱) #6--I(integer)
(5)07 tag值7 對應constant_class_info, 佔據2個位元組
00 16 即22 com/lesson/model/MyTest類全限定名稱
#3 = Class #22 // com/lesson/model/MyTest
(6) 07 tag值7對應constant_class_info, 佔據2個位元組
00 17即23 com/lang/object類全限定名稱
(7)01 tag值1 對應constant_utf-8_info, 佔據3個位元組
前兩個表示utf-8編碼字元長度。00 01 即1個位元組
00 01 後面一個位元組是61。61就是a(asc 碼)
(8) 01 tag值1
………………..就這樣一點一點分析就明白了位元組碼
縱觀java位元組碼的整體結構由下面10個部分構成,精確的描述了位元組碼資訊
try-finally分析:
public static String test(){
String res = "he";
try {
return res;
} finally {
res = "res";
}
}
0: ldc #5 // String he he壓入棧頂
2: astore_0 //he 存到本地變數0中
3: aload_0 //本地變數1中壓棧
4: astore_1 //he 存到本地變數1中
5: ldc #6 // String res res壓入棧頂
7: astore_0 //res 存到本地變數0中
8: aload_1 //本地變數1中壓棧
9: areturn //返回本地變數1 he
10: astore_2
11: ldc #6 // String res
13: astore_0
14: aload_2
15: athrow