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