1.Class文件基礎
(1)文件格式
Class文件的結構不像XML等描述語言那樣松散自由。由於它沒有任何分隔符號,
所以,以上數據項無論是順序還是數量都是被嚴格限定的。哪個字節代表什么
含義,長度是多少,先后順序如何,都不允許改變。
(2)數據類型
仔細觀察上面的Class文件格式,可以看出Class文件格式采用一種類似於C語言
結構體的偽結構來存儲,這種偽結構中只有兩種數據類型:
無符號數和表。
無符號數就是u1、u2、u4、u8來分別代表1個、2個、4個、8個字節。表是由
多個無符號數或其他表構成的復合數據類型,以“_info”結尾。在表開始位置,
通常會使用一個前置的容量計數器,因為表通常要描述數量不定的多個數據。
下圖表示的就是Class文件格式中按順序各個數據項的類型:
(3)兼容性
高版本的JDK能向下兼容以前版本的Class文件,但不能運行以后版本的Class文件,
即使文件格式未發生任何變化。舉例來說,JDK 1.7中的JRE能夠執行JDK 1.5編譯
出的Class文件,但是JDK 1.7編譯出來的Class文件不能被JDK 1.5使用。這就是
target參數的用處,可以在使用JDK 1.7編譯時指定-target 1.5。
2.一個簡單的例子
- package com.cdai.jvm.bytecode;
- public class ByteCodeSample {
- private String msg = "hello world";
- public void say() {
- System.out.println(msg);
- }
- }
編譯成Class文件后的樣子:
3.逐個字節分析
(1)魔數和版本號
前四個字節(u4)cafebabe就是Class文件的魔數,第5、6字節(u2)是Class文件的
次版本號,第7、8字節(u2)是主版本號。十六進制0和32,也就是版本號為50.0,
即JDK 1.6。之前介紹的target參數會影響這四個字節的值,從而使Class文件兼容不同
的JDK版本。
(2)常量池
常量池是一個表結構,並且就像之前介紹過的,在表的內容前有一個u2類型的計數器,
表示常量池的長度。十六進制23的十進制值為35,表示常量池里有下標為1~34的表項。
下標從1開始而不是0,是因為第0個表項表示“不引用常量池中的任意一項”。每個表項
的第一個字節是一個u1類型,表示12中數據類型。具體含義如下:
以第一項
07 00 02為例,07表示該常量是個CONSTANT_Class_info類型,緊接着一個u2
類型的索引執行第2項常量。再看第二項
01 00 24 63 6f 6d 2f ... 65表示的就是字符串
類型,長度為36(十六進制00 24),緊接着就是UTF-8編碼的字符串"com/cdai/jvm/bytecode
/ByteCodeSample"。很容易讀懂吧?常量池主要是為后面的字段表和方法表服務的。
下面是通過javap解析后常量池的全貌(執行
javap -c -l -s -verbose ByteCodeSample)
Constant pool:
const #1 = class #2; // com/cdai/jvm/bytecode/ByteCodeSample
const #2 = Asciz com/cdai/jvm/bytecode/ByteCodeSample;
const #3 = class #4; // java/lang/Object
const #4 = Asciz java/lang/Object;
const #5 = Asciz msg;
const #6 = Asciz Ljava/lang/String;;
const #7 = Asciz <init>;
const #8 = Asciz ()V;
const #9 = Asciz Code;
const #10 = Method #3.#11; // java/lang/Object."<init>":()V
const #11 = NameAndType #7:#8;// "<init>":()V
const #12 = String #13; // hello world
const #13 = Asciz hello world;
const #14 = Field #1.#15; // com/cdai/jvm/bytecode/ByteCodeSample.msg:Ljava/lang/String;
const #15 = NameAndType #5:#6;// msg:Ljava/lang/String;
const #16 = Asciz LineNumberTable;
const #17 = Asciz LocalVariableTable;
const #18 = Asciz this;
const #19 = Asciz Lcom/cdai/jvm/bytecode/ByteCodeSample;;
const #20 = Asciz say;
const #21 = Field #22.#24; // java/lang/System.out:Ljava/io/PrintStream;
const #22 = class #23; // java/lang/System
const #23 = Asciz java/lang/System;
const #24 = NameAndType #25:#26;// out:Ljava/io/PrintStream;
const #25 = Asciz out;
const #26 = Asciz Ljava/io/PrintStream;;
const #27 = Method #28.#30; // java/io/PrintStream.println:(Ljava/lang/String;)V
const #28 = class #29; // java/io/PrintStream
const #29 = Asciz java/io/PrintStream;
const #30 = NameAndType #31:#32;// println:(Ljava/lang/String;)V
const #31 = Asciz println;
const #32 = Asciz (Ljava/lang/String;)V;
const #33 = Asciz SourceFile;
const #34 = Asciz ByteCodeSample.java;
const #1 = class #2; // com/cdai/jvm/bytecode/ByteCodeSample
const #2 = Asciz com/cdai/jvm/bytecode/ByteCodeSample;
const #3 = class #4; // java/lang/Object
const #4 = Asciz java/lang/Object;
const #5 = Asciz msg;
const #6 = Asciz Ljava/lang/String;;
const #7 = Asciz <init>;
const #8 = Asciz ()V;
const #9 = Asciz Code;
const #10 = Method #3.#11; // java/lang/Object."<init>":()V
const #11 = NameAndType #7:#8;// "<init>":()V
const #12 = String #13; // hello world
const #13 = Asciz hello world;
const #14 = Field #1.#15; // com/cdai/jvm/bytecode/ByteCodeSample.msg:Ljava/lang/String;
const #15 = NameAndType #5:#6;// msg:Ljava/lang/String;
const #16 = Asciz LineNumberTable;
const #17 = Asciz LocalVariableTable;
const #18 = Asciz this;
const #19 = Asciz Lcom/cdai/jvm/bytecode/ByteCodeSample;;
const #20 = Asciz say;
const #21 = Field #22.#24; // java/lang/System.out:Ljava/io/PrintStream;
const #22 = class #23; // java/lang/System
const #23 = Asciz java/lang/System;
const #24 = NameAndType #25:#26;// out:Ljava/io/PrintStream;
const #25 = Asciz out;
const #26 = Asciz Ljava/io/PrintStream;;
const #27 = Method #28.#30; // java/io/PrintStream.println:(Ljava/lang/String;)V
const #28 = class #29; // java/io/PrintStream
const #29 = Asciz java/io/PrintStream;
const #30 = NameAndType #31:#32;// println:(Ljava/lang/String;)V
const #31 = Asciz println;
const #32 = Asciz (Ljava/lang/String;)V;
const #33 = Asciz SourceFile;
const #34 = Asciz ByteCodeSample.java;
(3)訪問標志
顯然,00 21表示的就是公有的類。
(4)類、父類、接口
這三個u2類型的值分別表示類索引1、父類索引3、接口索引集合0。查看之前的常量池,
第1項為"com/cdai/jvm/bytecode/ByteCodeSample",第3項為"java/lang/Object"。第0項
表示此類沒有實現任何接口,這也就是常量池第0項的作用!
(5)字段表
00 01表示有1個字段。00 02是字段的訪問標志,表示private權限的。00 05是字段的名稱
索引,指向常量池里第5項"msg"。00 06是字段的
描述符索引,指向常量池里的第6項
"Ljava/lang/String"。最后的00 00表示該字段沒有其他
屬性表了。
描述符的作用就是用來描述字段的數據類型、方法的參數列表和返回值。而屬性表就是為
字段表和方法表提供額外信息的表結構。對於字段來說,此處如果將字段聲明為一個static
final msg = "aaa"的常量,則字段后就會跟着一個屬性表,其中存在一項名為ConstantValue,
指向常量池中的一個常量,值為的"aaa"。
屬性表不像Class文件中的其他數據項那樣具有嚴格的順序、長度和內容,任何人實現的編譯器
都可以向屬性表中寫入自己定義的屬性信息,JVM會忽略掉它不認識的屬性。后面的方法表中
還要用到屬性表的Code屬性,來保存方法的字節碼。
(6)方法表
00 02表示有兩個方法。00 01是方法的訪問標志,表示公有方法。00 07和00 08與字段表中的名稱
和描述符索引相同,在這里分別表示"<init>"和"()V"。00 01表示該方法有屬性表,屬性名稱為00 09
即我們前面提到的Code屬性。
要注意的是:Code屬性表也可以有自己的屬性,如后面的LocalVariableTable和LineNumberTable。
它們分別為JVM提供方法的棧信息和調試信息。
以下是javap解析后的結果:
public com.cdai.jvm.bytecode.ByteCodeSample();
Signature: ()V
LineNumberTable:
line 3: 0
line 5: 4
line 3: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/cdai/jvm/bytecode/ByteCodeSample;
Code:
Stack=2, Locals=1, Args_size=1
0: aload_0
1: invokespecial #10; //Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #12; //String hello world
7: putfield #14; //Field msg:Ljava/lang/String;
10: return
LineNumberTable:
line 3: 0
line 5: 4
line 3: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/cdai/jvm/bytecode/ByteCodeSample;
public void say();
Signature: ()V
LineNumberTable:
line 8: 0
line 9: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/cdai/jvm/bytecode/ByteCodeSample;
Code:
Stack=2, Locals=1, Args_size=1
0: getstatic #21; //Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #14; //Field msg:Ljava/lang/String;
7: invokevirtual #27; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: return
LineNumberTable:
line 8: 0
line 9: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/cdai/jvm/bytecode/ByteCodeSample;
Signature: ()V
LineNumberTable:
line 3: 0
line 5: 4
line 3: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/cdai/jvm/bytecode/ByteCodeSample;
Code:
Stack=2, Locals=1, Args_size=1
0: aload_0
1: invokespecial #10; //Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #12; //String hello world
7: putfield #14; //Field msg:Ljava/lang/String;
10: return
LineNumberTable:
line 3: 0
line 5: 4
line 3: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/cdai/jvm/bytecode/ByteCodeSample;
public void say();
Signature: ()V
LineNumberTable:
line 8: 0
line 9: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/cdai/jvm/bytecode/ByteCodeSample;
Code:
Stack=2, Locals=1, Args_size=1
0: getstatic #21; //Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #14; //Field msg:Ljava/lang/String;
7: invokevirtual #27; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: return
LineNumberTable:
line 8: 0
line 9: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/cdai/jvm/bytecode/ByteCodeSample;
4.小結
怎么樣並不難吧!接下來我們將要學習下如何用字節碼工具如ASM、CGLIB來手寫
字節碼,從而加深對字節碼的理解。
