Class字节码内容学习
2015-12-13 10:24
204 查看
今天看了一下class文件的字节码内容,做个记录。先看下java的文件:
将此文件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
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
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
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"
这是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
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"
对比一下,第一个为MethodRef,指向初始化int方法,后面是类自身的Fieldref,再往后面就是一个class表,偏移量是指向的自身class的描述表信息,后面紧跟一个class表,指向其父类。同时可以看到父类中private int m;以及public static int lala = 2;,在子类的常量池中并没有相应的Filedref表,由此可知,子类继承父类的时候,其父类的method和filed是不会写入子类的常量池的。我们修改一下SubClass的内容。
然后编译,看下class文件内容:
可以看到,我们在方法里new的HashMap。会在常量池里有所体现,
#9 = Class #34 // java/util/HashMap
#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。
每个栈帧都包含一个指向运行时常量池中该栈帧锁属方法对引用,持有这个引用是为了支持方法调用过程中对动态链接。
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
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
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
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"
这是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
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"
对比一下,第一个为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
#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。
每个栈帧都包含一个指向运行时常量池中该栈帧锁属方法对引用,持有这个引用是为了支持方法调用过程中对动态链接。
相关文章推荐
- NDK/JNI学习--进口hello-jniproject
- 【校内互测】笨笨当火炬手
- VS提示C++某些头文件或函数已经过期无法使用
- jbpm4.4生成18张数据库表
- zhphp框架 文件上传类
- C# .ToString() 格式化
- android SQLite中query的用法
- 笨笨当火炬手
- 二维数组排列
- (转)负载均衡,回话保持,cookie
- Chrome自定义缩放百分比
- C++和python使用struct传输二进制数据结构来实现
- PhotoView点击事件
- malloc()和calloc()区别 以及memset() http://blog.csdn.net/ermuzhi/article/details/7833701
- php 生成 ueediter 网页编辑器
- bzoj-4318 OSU! 【数学期望】
- 关于操作系统中pv操作的实现
- 杭电ACM1021
- zhphp framework (二十八) token 加密与解密
- 异常java.lang.Thread.dumpStack(Unknown Source)