Java編譯後生成的.class字節碼文件裏面的內容究竟是什麼呢?一直比較困擾,現在終於看到了廬山真面目,比如對於test.class使用javap -p -verbose test可以查看生成的字節碼裏面的內容。用一個簡單的test類來分析字節碼裏面的內容。
test.java:
/**
*
* @author :zhengrf1
* @date 創建時間:2017年7月19日 上午10:53:08
* @version 1.0
* @parameter
* @since
* @return
*/
public class test {
private int a = 99;
public Long b = 88l;
String d = "hello";
Object e = new Object();
static Object c = new Object();
static String f ="world";
{
a=999;
}
static{
f ="world2";
}
test(int param){
a = param;
}
public static void fun(Stringstr){
System.out.println(str);
}
/**
*
* @author : zhengrf1
* @date 創建時間:2017年7月19日 上午10:53:08
*/
public static void main(String[]args) {
// TODOAuto-generated method stub
test t= newtest(999);
test.fun(t.d);
}
}
源碼很簡單,主要內容就是1.定義了幾個非靜態成員變量和靜態成員變量,2.並在非靜態語句塊{}和靜態語句塊static{}中重新賦值,3.重載了構造方法,4.創建了一個靜態成員方法fun,5.在main方法中創建了一個test對象並調用了靜態方法fun
執行了javap -verbose test命令後,生成test.class的詳細內容信息:
Classfile/D:/javadevelop/eclipse/user/workspace/RedisDao/bin/test.class
Last modified 2017-7-19; size 1116 bytes
MD5 checksum a211a3dc33eaac639dc28689a541fdf4
Compiled from "test.java"
publicclass test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constantpool:
#1 = Class #2 // test
#2 = Utf8 test
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8 b
#8 = Utf8 Ljava/lang/Long;
#9 = Utf8 d
#10 = Utf8 Ljava/lang/String;
#11 = Utf8 e
#12 = Utf8 Ljava/lang/Object;
#13 = Utf8 c
#14 = Utf8 f
#15 = Utf8 <clinit>
#16 = Utf8 ()V
#17 = Utf8 Code
#18 = Methodref #3.#19 //java/lang/Object."<init>":()V
#19 = NameAndType #20:#16 // "<init>":()V
#20 = Utf8 <init>
#21 = Fieldref #1.#22 // test.c:Ljava/lang/Object;
#22 = NameAndType #13:#12 // c:Ljava/lang/Object;
#23 = String #24 // world
#24 = Utf8 world
#25 = Fieldref #1.#26 // test.f:Ljava/lang/String;
#26 = NameAndType #14:#10 // f:Ljava/lang/String;
#27 = String #28 // world2
#28 = Utf8 world2
#29 = Utf8 LineNumberTable
#30 = Utf8 LocalVariableTable
#31 = Utf8 (I)V
#32 = Fieldref #1.#33 // test.a:I
#33 = NameAndType #5:#6 // a:I
#34 = Long 88l
#36 = Methodref #37.#39 // java/lang/Long.valueOf:(J)Ljava/lan
g/Long;
#37 = Class #38 // java/lang/Long
#38 = Utf8 java/lang/Long
#39 = NameAndType #40:#41 // valueOf:(J)Ljava/lang/Long;
#40 = Utf8 valueOf
#41 = Utf8 (J)Ljava/lang/Long;
#42 = Fieldref #1.#43 // test.b:Ljava/lang/Long;
#43 = NameAndType #7:#8 // b:Ljava/lang/Long;
#44 = String #45 // hello
#45 = Utf8 hello
#46 = Fieldref #1.#47 // test.d:Ljava/lang/String;
#47 = NameAndType #9:#10 // d:Ljava/lang/String;
#48 = Fieldref #1.#49 // test.e:Ljava/lang/Object;
#49 = NameAndType #11:#12 // e:Ljava/lang/Object;
#50 = Utf8 this
#51 = Utf8 Ltest;
#52 = Utf8 param
#53 = Utf8 fun
#54 = Utf8 (Ljava/lang/String;)V
#55 = Fieldref #56.#58 // java/lang/System.out:Ljava/io/Print
Stream;
#56 = Class #57 // java/lang/System
#57 = Utf8 java/lang/System
#58 = NameAndType #59:#60 // out:Ljava/io/PrintStream;
#59 = Utf8 out
#60 = Utf8 Ljava/io/PrintStream;
#61 = Methodref #62.#64 // java/io/PrintStream.println:(Ljava/
lang/String;)V
#62 = Class #63 // java/io/PrintStream
#63 = Utf8 java/io/PrintStream
#64 = NameAndType #65:#54 // println:(Ljava/lang/String;)V
#65 = Utf8 println
#66 = Utf8 str
#67 = Utf8 main
#68 = Utf8 ([Ljava/lang/String;)V
#69 = Methodref #1.#70 // test."<init>":(I)V
#70 = NameAndType #20:#31 // "<init>":(I)V
#71 = Methodref #1.#72 // test.fun:(Ljava/lang/String;)V
#72 = NameAndType #53:#54 // fun:(Ljava/lang/String;)V
#73 = Utf8 args
#74 = Utf8 [Ljava/lang/String;
#75 = Utf8 t
#76 = Utf8 SourceFile
#77 = Utf8 test.java
{
private int a;
descriptor: I
flags: ACC_PRIVATE
publicjava.lang.Long b;
descriptor: Ljava/lang/Long;
flags: ACC_PUBLIC
java.lang.String d;
descriptor: Ljava/lang/String;
flags:
java.lang.Object e;
descriptor: Ljava/lang/Object;
flags:
static java.lang.Object c;
descriptor: Ljava/lang/Object;
flags: ACC_STATIC
static java.lang.String f;
descriptor: Ljava/lang/String;
flags: ACC_STATIC
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: new #3 // class java/lang/Object
3: dup
4: invokespecial #18 // Method java/lang/Object."<init>
":()V
7: putstatic #21 // Field c:Ljava/lang/Object;
10: ldc #23 // String world
12: putstatic #25 // Field f:Ljava/lang/String;
15: ldc #27 // String world2
17: putstatic #25 // Field f:Ljava/lang/String;
20: return
LineNumberTable:
line 15: 0
line 16: 10
line 21: 15
line 22: 20
LocalVariableTable:
Start Length Slot Name Signature
test(int);
descriptor: (I)V
flags:
Code:
stack=3, locals=2, args_size=2
0: aload_0
1: invokespecial #18 // Methodjava/lang/Object."<init>
":()V
4: aload_0
5: bipush 99
7: putfield #32 // Field a:I
10: aload_0
11: ldc2_w #34 // long 88l
14: invokestatic #36 // Methodjava/lang/Long.valueOf:(
J)Ljava/lang/Long;
17: putfield #42 // Field b:Ljava/lang/Long;
20: aload_0
21: ldc #44 // String hello
23: putfield #46 // Field d:Ljava/lang/String;
26: aload_0
27: new #3 // class java/lang/Object
30: dup
31: invokespecial #18 // Method java/lang/Object."<init>
":()V
34: putfield #48 // Field e:Ljava/lang/Object;
37: aload_0
38: sipush 999
41: putfield #32 // Field a:I
44: aload_0
45: iload_1
46: putfield #32 // Field a:I
49: return
LineNumberTable:
line 24: 0
line 11: 4
line 12: 10
line 13: 20
line 14: 26
line 18: 37
line 25: 44
line 26: 49
LocalVariableTable:
Start Length Slot Name Signature
0 50 0 this Ltest;
0 50 1 param I
public static void fun(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #55 // Field java/lang/System.out:Ljav
a/io/PrintStream;
3: aload_0
4: invokevirtual #61 // Methodjava/io/PrintStream.prin
tln:(Ljava/lang/String;)V
7: return
LineNumberTable:
line 29: 0
line 30: 7
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 str Ljava/lang/String;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: new #1 // class test
3: dup
4: sipush 999
7: invokespecial #69 // Method"<init>":(I)V
10: astore_1
11: aload_1
12: getfield #46 // Field d:Ljava/lang/String;
15: invokestatic #71 // Methodfun:(Ljava/lang/String;)
V
18: return
LineNumberTable:
line 38: 0
line 39: 11
line 40: 18
LocalVariableTable:
Start Length Slot Name Signature
0 19 0 args [Ljava/lang/String;
11 8 1 t Ltest;
}
SourceFile: "test.java"
裏面的內容看似雜亂,其實稍微分析下就會很清楚了
第一部分深紅色模塊:
Classfile/D:/javadevelop/eclipse/user/workspace/RedisDao/bin/test.class
Last modified 2017-7-19; size 1116 bytes
MD5 checksum a211a3dc33eaac639dc28689a541fdf4
Compiled from "test.java"
--就是關於.calss文件的文件本身的信息介紹,有最後修改時間,文件大小,MD5校驗碼,來自哪個.java文件
第二部分紅色模塊:
publicclass test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
--就是關於test這個類的信息,包括編譯版本,和flags:修飾符訪問權限,可見test是ACC_PUBLIC(public)
第三部分藍色模塊:
Constantpool:
#1 = Class #2 // test
#2 = Utf8 test
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8 b
#8 = Utf8 Ljava/lang/Long;
#9 = Utf8 d
#10 = Utf8 Ljava/lang/String;
#11 = Utf8 e
#12 = Utf8 Ljava/lang/Object;
#13 = Utf8 c
#14 = Utf8 f
#15 = Utf8 <clinit>
#16 = Utf8 ()V
#17 = Utf8 Code
#18 = Methodref #3.#19 //java/lang/Object."<init>":()V
#19 = NameAndType #20:#16 // "<init>":()V
#20 = Utf8 <init>
#21 = Fieldref #1.#22 // test.c:Ljava/lang/Object;
#22 = NameAndType #13:#12 // c:Ljava/lang/Object;
#23 = String #24 // world
#24 = Utf8 world
#25 = Fieldref #1.#26 // test.f:Ljava/lang/String;
#26 = NameAndType #14:#10 // f:Ljava/lang/String;
#27 = String #28 // world2
#28 = Utf8 world2
#29 = Utf8 LineNumberTable
#30 = Utf8 LocalVariableTable
#31 = Utf8 (I)V
#32 = Fieldref #1.#33 // test.a:I
#33 = NameAndType #5:#6 // a:I
#34 = Long 88l
#36 = Methodref #37.#39 // java/lang/Long.valueOf:(J)Ljava/lan
g/Long;
#37 = Class #38 // java/lang/Long
#38 = Utf8 java/lang/Long
#39 = NameAndType #40:#41 // valueOf:(J)Ljava/lang/Long;
#40 = Utf8 valueOf
#41 = Utf8 (J)Ljava/lang/Long;
#42 = Fieldref #1.#43 // test.b:Ljava/lang/Long;
#43 = NameAndType #7:#8 // b:Ljava/lang/Long;
#44 = String #45 // hello
#45 = Utf8 hello
#46 = Fieldref #1.#47 // test.d:Ljava/lang/String;
#47 = NameAndType #9:#10 // d:Ljava/lang/String;
#48 = Fieldref #1.#49 // test.e:Ljava/lang/Object;
#49 = NameAndType #11:#12 // e:Ljava/lang/Object;
#50 = Utf8 this
#51 = Utf8 Ltest;
#52 = Utf8 param
#53 = Utf8 fun
#54 = Utf8 (Ljava/lang/String;)V
#55 = Fieldref #56.#58 // java/lang/System.out:Ljava/io/Print
Stream;
#56 = Class #57 // java/lang/System
#57 = Utf8 java/lang/System
#58 = NameAndType #59:#60 // out:Ljava/io/PrintStream;
#59 = Utf8 out
#60 = Utf8 Ljava/io/PrintStream;
#61 = Methodref #62.#64 // java/io/PrintStream.println:(Ljava/
lang/String;)V
#62 = Class #63 // java/io/PrintStream
#63 = Utf8 java/io/PrintStream
#64 = NameAndType #65:#54 // println:(Ljava/lang/String;)V
#65 = Utf8 println
#66 = Utf8 str
#67 = Utf8 main
#68 = Utf8 ([Ljava/lang/String;)V
#69 = Methodref #1.#70 // test."<init>":(I)V
#70 = NameAndType #20:#31 // "<init>":(I)V
#71 = Methodref #1.#72 // test.fun:(Ljava/lang/String;)V
#72 = NameAndType #53:#54 // fun:(Ljava/lang/String;)V
#73 = Utf8 args
#74 = Utf8 [Ljava/lang/String;
#75 = Utf8 t
#76 = Utf8 SourceFile
#77 = Utf8 test.java
---這就是大名鼎鼎的常量池了,裏面包含了很多類型的常量,比如
#1 =Class #2 // test
#2 =Utf8 test
--這個意思是在代碼區用到的#1這個索引買就是用到了一個Class對象,這個Class對象就是test,簡單來說就是#1就是Class,對一個類或接口的符號引用,而這個Class的名就是#2,而#2就是test
再比如後面的代碼內容中有一行是:
4: invokespecial #18 // Methodjava/lang/Object."<init>":()V
--來我們分析下是什麼回事,首先看命令invokespecial(調用需要特殊處理的實例方法:invokespecial),說明這行命令是調用一個實例方法,調用哪個方法呢?調用常量池中的#18,再去看看#18的內容:
#18 = Methodref #3.#19 //java/lang/Object."<init>":()V
--#18是一個Methodref類型常量,意思這個常量是對一個類中方法的符號引用,而#18由#3.#19組成,那看看#3和#19
#3 =Class #4 // java/lang/Object
#4 =Utf8 java/lang/Object
#19 =NameAndType #20:#16 // "<init>":()V
#20 =Utf8 <init>
#16 =Utf8 ()V
--#3是Class,對一個類或接口的符號引用,那個類名由#4確定,#4對應java/lang/Object,說明#3全名就是java/lang/Object這個類型,而#19是NameAndType, 對一個字段或方法的部分符號引用,而引用的方法由
#20:#16組成,而#20和#16是Utf8,utf-8編碼的字符串。分別是<init>和()V,其中<init>是構造方法的方法名,而()V是方法的簽名,表示方法是個沒有參數返回類型爲void。所以#18的最後的全名是java/lang/Object."<init>":()V,表示這是個Object類的默認構造方法。這是查找的過程,其實javap生成的結果中#18常量池這行已經在//後面幫我們拼湊出了最後的全名
--說到這裏,所謂的神祕的常量池就被我們扒的差不多了,但是常量池的作用是什麼呢?就以上面4: invokespecial #18例子看,當我們加載Class文件到JVM中,常量池的內容會存放在方法區的常量池區域,當我們執行代碼到invokespecial #18這行時,JVM就知道要去找Object類的構造方法init,如果Object類還沒加載,那就加載Object.class進jvm,加載Object.class後,很明顯JVM將在方法區中存儲Object類的所有方法,包括構造方法init,這樣這個方法的物理內存地址就確定了,假設這個地址是ff0809,那麼這個ff0809這個值就會回寫到常量池中,假設是#18 = Methodref #3.#19 ff0809,那麼後續只要其他代碼執行到invokespecial #18,就直接調用ff0809這個方法入口就行,不需要再次去解析。這個就是常量池的符號引用解析成內存直接引用的一步。
第四部分綠色部分:
private int a;
descriptor: I
flags: ACC_PRIVATE
publicjava.lang.Long b;
descriptor: Ljava/lang/Long;
flags: ACC_PUBLIC
java.lang.String d;
descriptor: Ljava/lang/String;
flags:
java.lang.Object e;
descriptor: Ljava/lang/Object;
flags:
static java.lang.Object c;
descriptor: Ljava/lang/Object;
flags: ACC_STATIC
static java.lang.String f;
descriptor: Ljava/lang/String;
flags: ACC_STATIC
--這個就是類的靜態成員和非靜態成員的信息描述,其中以static String f爲例子,可見,生產的字節碼中是下面描述:
staticjava.lang.String f;
descriptor: Ljava/lang/String;
flags: ACC_STATIC
-- descriptor表示成員變量的簽名,而flags: ACC_STATIC表示成員變量的訪問權限和靜態屬性。其中可以看到訪問權限是空,也就是使用了默認的包訪問權限。
第五部分紫色部分:
static{};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: new #3 // class java/lang/Object
3: dup
4: invokespecial #18 // Method java/lang/Object."<init>
":()V
7: putstatic #21 // Field c:Ljava/lang/Object;
10: ldc #23 // String world
12: putstatic #25 // Field f:Ljava/lang/String;
15: ldc #27 // String world2
17: putstatic #25 // Field f:Ljava/lang/String;
20: return
LineNumberTable:
line 15: 0
line 16: 10
line 21: 15
line 22: 20
LocalVariableTable:
Start Length Slot Name Signature
test(int);
descriptor: (I)V
flags:
Code:
stack=3, locals=2, args_size=2
0: aload_0
1: invokespecial #18 // Methodjava/lang/Object."<init>
":()V
4: aload_0
5: bipush 99
7: putfield #32 // Field a:I
10: aload_0
11: ldc2_w #34 // long 88l
14: invokestatic #36 // Methodjava/lang/Long.valueOf:(
J)Ljava/lang/Long;
17: putfield #42 // Field b:Ljava/lang/Long;
20: aload_0
21: ldc #44 // String hello
23: putfield #46 // Field d:Ljava/lang/String;
26: aload_0
27: new #3 // class java/lang/Object
30: dup
31: invokespecial #18 // Method java/lang/Object."<init>
":()V
34: putfield #48 // Field e:Ljava/lang/Object;
37: aload_0
38: sipush 999
41: putfield #32 // Field a:I
44: aload_0
45: iload_1
46: putfield #32 // Field a:I
49: return
LineNumberTable:
line 24: 0
line 11: 4
line 12: 10
line 13: 20
line 14: 26
line 18: 37
line 25: 44
line 26: 49
LocalVariableTable:
Start Length Slot Name Signature
0 50 0 this Ltest;
0 50 1 param I
public static void fun(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #55 // Field java/lang/System.out:Ljav
a/io/PrintStream;
3: aload_0
4: invokevirtual #61 // Methodjava/io/PrintStream.prin
tln:(Ljava/lang/String;)V
7: return
LineNumberTable:
line 29: 0
line 30: 7
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 str Ljava/lang/String;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: new #1 // class test
3: dup
4: sipush 999
7: invokespecial #69 // Method"<init>":(I)V
10: astore_1
11: aload_1
12: getfield #46 // Field d:Ljava/lang/String;
15: invokestatic #71 // Methodfun:(Ljava/lang/String;)
V
18: return
LineNumberTable:
line 38: 0
line 39: 11
line 40: 18
LocalVariableTable:
Start Length Slot Name Signature
0 19 0 args [Ljava/lang/String;
11 8 1 t Ltest;
--這就是test類的方法代碼模塊,拿構造方法爲例子:
test(int);
descriptor: (I)V
flags:
Code:
stack=3, locals=2, args_size=2
0: aload_0
1: invokespecial #18 // Methodjava/lang/Object."<init>
":()V
4: aload_0
5: bipush 99
7: putfield #32 // Field a:I
10: aload_0
11: ldc2_w #34 // long 88l
14: invokestatic #36 // Methodjava/lang/Long.valueOf:(
J)Ljava/lang/Long;
17: putfield #42 // Field b:Ljava/lang/Long;
20: aload_0
21: ldc #44 // String hello
23: putfield #46 // Field d:Ljava/lang/String;
26: aload_0
27: new #3 // class java/lang/Object
30: dup
31: invokespecial #18 // Method java/lang/Object."<init>
":()V
34: putfield #48 // Field e:Ljava/lang/Object;
37: aload_0
38: sipush 999
41: putfield #32 // Field a:I
44: aload_0
45: iload_1
46: putfield #32 // Field a:I
49: return
LineNumberTable:
line 24: 0
line 11: 4
line 12: 10
line 13: 20
line 14: 26
line 18: 37
line 25: 44
line 26: 49
LocalVariableTable:
Start Length Slot Name Signature
0 50 0 this Ltest;
0 50 1 param I
--可以看到descriptor: (I)V是方法的簽名,表示有一個int類型的參數,返回類型是void的方法,flags:表示訪問權限和靜態屬性,可見改方法是默認的包訪問權限和非靜態,Code:
stack=3, locals=2, args_size=2
0: aload_0
1: invokespecial #18 // Methodjava/lang/Object."<init>
":()V
4: aload_0
5: bipush 99
7: putfield #32 // Field a:I
10: aload_0
11: ldc2_w #34 // long 88l
14: invokestatic #36 // Methodjava/lang/Long.valueOf:(
J)Ljava/lang/Long;
17: putfield #42 // Field b:Ljava/lang/Long;
20: aload_0
21: ldc #44 // String hello
23: putfield #46 // Field d:Ljava/lang/String;
26: aload_0
27: new #3 // class java/lang/Object
30: dup
31: invokespecial #18 // Method java/lang/Object."<init>
":()V
34: putfield #48 // Field e:Ljava/lang/Object;
37: aload_0
38: sipush 999
41: putfield #32 // Field a:I
44: aload_0
45: iload_1
46: putfield #32 // Field a:I
49: return
--其實就是代碼命令了,stack=3,locals=2, args_size=2,stack應該表示棧深,locals應該表示局部變量個數,args_size表示參數個數,分別是this和param。
0: aload_0
1:invokespecial #18 //Method java/lang/Object."<init>":()V
--這兩行是啥意思呢?其實這是編譯器自己添加的代碼,調用父類的構造方法,而test的父類就是上帝類Object。這說明,初始化之類之前必須初始化父類,如果你不寫,編譯器也會幫你寫。
4:aload_0
5:bipush 99
7:putfield #32 // Field a:I
--這個意思是把99壓入棧中並且賦值給成員變量#32,而#32在常量池中就是a。
10:aload_0
11:ldc2_w #34 // long 88l
14:invokestatic #36 // Methodjava/lang/Long.valueOf:(
J)Ljava/lang/Long;
17:putfield #42 // Field b:Ljava/lang/Long;
--這幾行意思就是把88l作爲參數傳入Long.valueOf(long)方法中,生成一個Long類型對象並且賦值給成員變量b,其實就是Long b = 88l;這行代碼,只不過編譯器使用了語法糖自動裝載幫我們增加了調用Long.valueOf的方法生成了Long類型對象。
20:aload_0
21:ldc #44 // String hello
23:putfield #46 // Field d:Ljava/lang/String;
--這幾行意思就是把#44所表示的“hello”字符串賦值給成員變量d,其實就是d = "hello";
26: aload_0
27:new #3 // class java/lang/Object
30: dup
31:invokespecial #18 //Method java/lang/Object."<init>":()V
34:putfield #48 // Field e:Ljava/lang/Object;
37:aload_0
38:sipush 999
41:putfield #32 // Field a:I
44:aload_0
45: iload_1
46:putfield #32 // Field a:I
49:return
--這幾行就不細說了,跟上面大同小異,分別表示代碼:
Object e = new Object();
a=999;
a = param;
--從上面的構造方法的解析可以看到,雖然我們在源碼中寫的構造方法是test(intparam){a = param;}
但是實際上編譯器生成的構造方法可不這麼簡單,1.首先是調用父類Object的構造方法。2.把成員變量定義時的初始化操作也添加進了構造方法中。3.把{}語句塊中的賦值操作也添加進構造方法中。4.最後一步纔是源碼中的代碼。知道這個步驟,我們會對初始化有更深的瞭解。
除了舉例子說明的構造方法外,可以看到還有其他方法,其中比較奇怪的是
static{};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: new #3 // class java/lang/Object
3: dup
4: invokespecial #18 // Method java/lang/Object."<init>
":()V
7: putstatic #21 // Field c:Ljava/lang/Object;
10: ldc #23 // String world
12: putstatic #25 // Field f:Ljava/lang/String;
15: ldc #27 // String world2
17: putstatic #25 // Field f:Ljava/lang/String;
20: return
--這個其實就是大名鼎鼎的clinit方法(不過奇怪的是爲什麼方法名不是直接寫clinit),由編譯器生成,作用就是初始化靜態成員變量,如上面代碼翻譯過來就是
static Object c = new Object();
static String f = "world";
f = "world2";
--剩下的其他方法就沒什麼好說了,和源碼一一對應
補充:
1. LineNumberTable:
line 24: 0
line 11: 4
line 12: 10
line 13: 20
line 14: 26
line 18: 37
line 25: 44
line 26: 49
LocalVariableTable:
Start Length Slot Name Signature
0 50 0 this Ltest;
0 50 1 param I
--這些是什麼?其實這些不是很重要,只是附加信息,其中LineNumberTable:是方法裏面的命令行數和.java源碼文件代碼行數的對應關係,比如line 12: 10就是源碼中的第12行代碼和字節碼中構造方法中CODE段的第10行命令。應該是爲了檢查排除bug使用。
2. LocalVariableTable:
Start Length Slot Name Signature
0 50 0 this Ltest;
0 50 1 param I
--這個又是什麼,也是附加信息,描述的是方法內部局部變量(包括參數)的信息,比如上面就是對應構造方法,一共有兩個局部變量,分別是隱藏的this和參數param,並且它們的作用域都是構造方法中命令行的0到50行,其實就是整個構造方法內有效。
3. 如果把static{}註釋掉,只要test內部有static變量,那麼編譯器還是會自動添加clinit方法,如果把所有static變量和語句塊註釋掉,那麼編譯器將不再生成clinit方法。只要存在一個static成員變量,則編譯器都會自動生成clinit方法,該方法的方法簽名和默認構造方法一模一樣,只是flag屬性是ACC_STATIC(靜態)
4. 如果再創建一個子類ChildTest繼承Test類,那麼字節碼有什麼變化嗎?應該說變化不大
public final classChildTest extends test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
--只是在對Class描述段,裏面顯示了ChildTest繼承於test,並且是final屬性,flags的內容也做相應修改。還有就是因爲test重載了構造方法,創造了一個帶參數的構造方法,所以父類test已經不存在默認構造方法,這就使子類必須手工聲明和調用super(int)來初始化父類。如果父類存在默認構造方法(也就是無參構造方法),則子類不必手工調用super(),編譯器會自動添加,有一點需注意:子類的static成員初始化在父類初始化之前,簡單來說clinit方法在init方法前執行,並且只在加載時執行一次。
總結:通過對class文件的分析,我們能更深入地去了解java
另外附三個擴展連接:裏面有對命令和名詞的更詳細說明,就是有個不好的地方,沒有總體的概念
http://blog.csdn.net/lisulong1/article/details/53001211
http://blog.csdn.net/xieyuooo/article/details/17452383