今天看了一下class文件的字节码内容,做个记录。先看下java的文件:
class TestClass{
private int m;
public static int lala = 2;
public int inc(){
return m+1;
}
}
class SubClass extends TestClass{
private int b=0;
private TestClass sub= null;
public int test(){
int cc = 0;
return cc;
}
}
将此文件javac编译,得到两个class文件,这里说下要用的工具:winhex十六进制工具 以及javap
简单的说下class文件结构。
class文件的前四位是固定的,称为魔数,CAFEBABE(咖啡宝贝),后面3位是主版本号,主版本号之后两位是次版本号。下一位是常量池容量。然后后面很长的内容都是常量池数据,常量池之后是code字节码数据。我们就不看十六进制数据了,直接看下javap为我们计算得到的class信息。
Classfile /C:/Users/nys/Desktop/TestClass.class
Last modified 2015-12-13; size 354 bytes
MD5 checksum fc317f27d60bc2718a828e8834854fe2
Compiled from "TestClass.java"
class TestClass
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Methodref #5.#18 // java/lang/Object."<init>":()V
#2 = Fieldref #4.#19 // TestClass.m:I
#3 = Fieldref #4.#20 // TestClass.lala:I
#4 = Class #21 // TestClass
#5 = Class #22 // java/lang/Object
#6 = Utf8 m
#7 = Utf8 I
#8 = Utf8 lala
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 inc
#14 = Utf8 ()I
#15 = Utf8 <clinit>
#16 = Utf8 SourceFile
#17 = Utf8 TestClass.java
#18 = NameAndType #9:#10 // "<init>":()V
#19 = NameAndType #6:#7 // m:I
#20 = NameAndType #8:#7 // lala:I
#21 = Utf8 TestClass
#22 = Utf8 java/lang/Object
{
public static int lala;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC
Last modified 2015-12-13; size 354 bytes
MD5 checksum fc317f27d60bc2718a828e8834854fe2
Compiled from "TestClass.java"
class TestClass
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Methodref #5.#18 // java/lang/Object."<init>":()V
#2 = Fieldref #4.#19 // TestClass.m:I
#3 = Fieldref #4.#20 // TestClass.lala:I
#4 = Class #21 // TestClass
#5 = Class #22 // java/lang/Object
#6 = Utf8 m
#7 = Utf8 I
#8 = Utf8 lala
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 inc
#14 = Utf8 ()I
#15 = Utf8 <clinit>
#16 = Utf8 SourceFile
#17 = Utf8 TestClass.java
#18 = NameAndType #9:#10 // "<init>":()V
#19 = NameAndType #6:#7 // m:I
#20 = NameAndType #8:#7 // lala:I
#21 = Utf8 TestClass
#22 = Utf8 java/lang/Object
{
public static int lala;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC
TestClass();
descriptor: ()V
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
descriptor: ()V
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public int inc();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field m:I
4: iconst_1
5: iadd
6: ireturn
LineNumberTable:
line 5: 0
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field m:I
4: iconst_1
5: iadd
6: ireturn
LineNumberTable:
line 5: 0
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: iconst_2
1: putstatic #3 // Field lala:I
4: return
LineNumberTable:
line 3: 0
}
SourceFile: "TestClass.java"
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: iconst_2
1: putstatic #3 // Field lala:I
4: return
LineNumberTable:
line 3: 0
}
SourceFile: "TestClass.java"
这是TestClass的class文件,我们看到主次版本号之后,有一个constant pool常量池,看下常量池内容。可以看到常量池的第一项是方法表(class文件由两部分组成,一是无回复无符号数,u1 u2 u4 u8,二是表,表是由多个无符号数和其它表构成的数据项,class文件本身就是一张表结构),指向偏移量#5.#18,我们往下可以看到#5是一个class表,指向#22,而#22就是java/lang/object,继续看下#18,可以看到是init<>f方法,所以我们可知,第一个methodref是object的init<>方法,后面的以此类推。下面是SubClass的文件内容
class SubClass extends TestClass
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Methodref #5.#18 // TestClass."<init>":()V
#2 = Fieldref #4.#19 // SubClass.b:I
#3 = Fieldref #4.#20 // SubClass.sub:LSubClass;
#4 = Class #21 // SubClass
#5 = Class #22 // TestClass
#6 = Utf8 b
#7 = Utf8 I
#8 = Utf8 sub
#9 = Utf8 LSubClass;
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 test
#15 = Utf8 ()I
#16 = Utf8 SourceFile
#17 = Utf8 TestClass.java
#18 = NameAndType #10:#11 // "<init>":()V
#19 = NameAndType #6:#7 // b:I
#20 = NameAndType #8:#9 // sub:LSubClass;
#21 = Utf8 SubClass
#22 = Utf8 TestClass
{
SubClass();
descriptor: ()V
flags:
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method TestClass."<init>":()V
4: aload_0
5: iconst_0
6: putfield #2 // Field b:I
9: aload_0
10: aconst_null
11: putfield #3 // Field sub:LSubClass;
14: return
LineNumberTable:
line 9: 0
line 10: 4
line 11: 9
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Methodref #5.#18 // TestClass."<init>":()V
#2 = Fieldref #4.#19 // SubClass.b:I
#3 = Fieldref #4.#20 // SubClass.sub:LSubClass;
#4 = Class #21 // SubClass
#5 = Class #22 // TestClass
#6 = Utf8 b
#7 = Utf8 I
#8 = Utf8 sub
#9 = Utf8 LSubClass;
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 test
#15 = Utf8 ()I
#16 = Utf8 SourceFile
#17 = Utf8 TestClass.java
#18 = NameAndType #10:#11 // "<init>":()V
#19 = NameAndType #6:#7 // b:I
#20 = NameAndType #8:#9 // sub:LSubClass;
#21 = Utf8 SubClass
#22 = Utf8 TestClass
{
SubClass();
descriptor: ()V
flags:
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method TestClass."<init>":()V
4: aload_0
5: iconst_0
6: putfield #2 // Field b:I
9: aload_0
10: aconst_null
11: putfield #3 // Field sub:LSubClass;
14: return
LineNumberTable:
line 9: 0
line 10: 4
line 11: 9
public int test();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: ireturn
LineNumberTable:
line 13: 0
line 14: 2
}
SourceFile: "TestClass.java"
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: ireturn
LineNumberTable:
line 13: 0
line 14: 2
}
SourceFile: "TestClass.java"
对比一下,第一个为MethodRef,指向初始化int方法,后面是类自身的Fieldref,再往后面就是一个class表,偏移量是指向的自身class的描述表信息,后面紧跟一个class表,指向其父类。同时可以看到父类中private int m;以及public static int lala = 2;,在子类的常量池中并没有相应的Filedref表,由此可知,子类继承父类的时候,其父类的method和filed是不会写入子类的常量池的。我们修改一下SubClass的内容。
class SubClass extends TestClass {
private int b = 0;
public TestClass sub = new SubClass();
public int test() {
System.out.println("b");
int cc = 0;
return cc;
}
public void println() {
Map map = new HashMap();
map.put("","");
sub.test();
}
}
然后编译,看下class文件内容:
<div>Classfile /C:/Users/nys/Desktop/SubClass.class
Last modified 2015-12-13; size 698 bytes
MD5 checksum bac3236b7989742ba907bf359a9ac30b
Compiled from "TestClass.java"
class SubClass extends TestClass
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Methodref #14.#28 // TestClass."<init>":()V
#2 = Fieldref #3.#29 // SubClass.b:I
#3 = Class #30 // SubClass
#4 = Methodref #3.#28 // SubClass."<init>":()V
#5 = Fieldref #3.#31 // SubClass.sub:LTestClass;
#6 = Fieldref #32.#33 // java/lang/System.out:Ljava/io/PrintStream;
#7 = String #15 // b
#8 = Methodref #34.#35 // java/io/PrintStream.println:(Ljava/lang/String;)V
#9 = Class #36 // java/util/HashMap
#10 = Methodref #9.#28 // java/util/HashMap."<init>":()V
#11 = String #37 //
#12 = InterfaceMethodref #38.#39 // java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
#13 = Methodref #14.#40 // TestClass.test:()I
#14 = Class #41 // TestClass
#15 = Utf8 b
#16 = Utf8 I
#17 = Utf8 sub
#18 = Utf8 LTestClass;
#19 = Utf8 <init>
#20 = Utf8 ()V
#21 = Utf8 Code
#22 = Utf8 LineNumberTable
#23 = Utf8 test
#24 = Utf8 ()I
#25 = Utf8 println
#26 = Utf8 SourceFile
#27 = Utf8 TestClass.java
#28 = NameAndType #19:#20 // "<init>":()V
#29 = NameAndType #15:#16 // b:I
#30 = Utf8 SubClass
#31 = NameAndType #17:#18 // sub:LTestClass;
#32 = Class #42 // java/lang/System
#33 = NameAndType #43:#44 // out:Ljava/io/PrintStream;
#34 = Class #45 // java/io/PrintStream
#35 = NameAndType #25:#46 // println:(Ljava/lang/String;)V
#36 = Utf8 java/util/HashMap
#37 = Utf8
#38 = Class #47 // java/util/Map
#39 = NameAndType #48:#49 // put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
#40 = NameAndType #23:#24 // test:()I
#41 = Utf8 TestClass
#42 = Utf8 java/lang/System
#43 = Utf8 out
#44 = Utf8 Ljava/io/PrintStream;
#45 = Utf8 java/io/PrintStream
#46 = Utf8 (Ljava/lang/String;)V
#47 = Utf8 java/util/Map
#48 = Utf8 put
#49 = Utf8 (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
{
public TestClass sub;
descriptor: LTestClass;
flags: ACC_PUBLIC</div><div>
</div><div> SubClass();
descriptor: ()V
flags:
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method TestClass."<init>":()V
4: aload_0
5: iconst_0
6: putfield #2 // Field b:I
9: aload_0
10: new #3 // class SubClass
13: dup
14: invokespecial #4 // Method "<init>":()V
17: putfield #5 // Field sub:LTestClass;
20: return
LineNumberTable:
line 20: 0
line 22: 4
line 24: 9</div><div>
</div><div> public int test();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #7 // String b
5: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: iconst_0
9: istore_1
10: iload_1
11: ireturn
LineNumberTable:
line 27: 0
line 28: 8
line 29: 10</div><div>
</div><div> public void println();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=2, args_size=1
0: new #9 // class java/util/HashMap
3: dup
4: invokespecial #10 // Method java/util/HashMap."<init>":()V
7: astore_1
8: aload_1
9: ldc #11 // String
11: ldc #11 // String
13: invokeinterface #12, 3 // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
18: pop
19: aload_0
20: getfield #5 // Field sub:LTestClass;
23: invokevirtual #13 // Method TestClass.test:()I
26: pop
27: return
LineNumberTable:
line 33: 0
line 34: 8
line 37: 19
line 38: 27
}
SourceFile: "TestClass.java"</div>
可以看到,我们在方法里new的HashMap。会在常量池里有所体现,
#9 = Class #34 // java/util/HashMap
#10 = Methodref #9.#26 // java/util/HashMap."<init>":()V
#10 = Methodref #9.#26 // java/util/HashMap."<init>":()V
#12 = InterfaceMethodref #38.#39 // java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
由此可知,在编译生成class文件时,也会将方法内部的类方法调用等信息放在常量池中形成表数据,在运行时确定直接引用(符号引用-->直接引用)。
我们看下code字节码的内容,注意看下map.put("","");这几生成的指令,如下:
13: invokeinterface #12, 3 // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
可以看到,这里调用的是invokeinterface指令,这里就是动态链接,运行时确定具体内存地址的方法。
另外,虚拟机的加载存储指令,用于将数据在栈帧中的局部变量表和操作数栈之间来回传输,load型号指令用于局部变量加载到操作数栈,store指令用于将数据从操作数栈存储到局部变量表。
注:locals参数是局部变量表的最大容量,单位Slot。局部变量表位于栈帧内,大小在编译器就已确定(比如locals参数),而栈帧中存放了局部变量表 操作数栈 动态连接 返回地址等信息。Slot中可存放 boolean byte char short int float reference 和returnAddress8种类型。其中reference是引用类型,此引用可以直接或间接的找到的内存地址,以及对象在方法区的class数据。局部变量表对Slot使索引定位的方式 进行管理,下表从0开始,0为存储this引用,剩下Slot按照形参顺序,从一开始进行分配,形参分配Slot后,在对方法内部参数进行Slot分配。Slot是可重用对。其字节大小并没有 明确对说明,可以存放32位以数据,而龙double需要使用两个Slot.Slot的复用回对gc产生影响,如果一个方法后面执行对时间较长,而前面又new 了大量对内存空间,此时这些内存不会被回收(如果这些Slot被复用了,那么堆区对内存就可回收,引用已不存在)。所以在不确定Slot是否会被后面变量复用对时候,最好手动置为null。
每个栈帧都包含一个指向运行时常量池中该栈帧锁属方法对引用,持有这个引用是为了支持方法调用过程中对动态链接。