编写一个简单的Class文件
public class TestClass {
private int m;
public int inc(){
return m++;
}
}
查看编译出来的Class文件二进制:
localhost:baseLearning jjchen$ xxd TestClass.class
00000000: cafe babe 0000 0034 0016 0a00 0400 1209 .......4........
00000010: 0003 0013 0700 1407 0015 0100 016d 0100 .............m..
00000020: 0149 0100 063c 696e 6974 3e01 0003 2829 .I...<init>...()
00000030: 5601 0004 436f 6465 0100 0f4c 696e 654e V...Code...LineN
00000040: 756d 6265 7254 6162 6c65 0100 124c 6f63 umberTable...Loc
00000050: 616c 5661 7269 6162 6c65 5461 626c 6501 alVariableTable.
00000060: 0004 7468 6973 0100 0b4c 5465 7374 436c ..this...LTestCl
00000070: 6173 733b 0100 0369 6e63 0100 0328 2949 ass;...inc...()I
00000080: 0100 0a53 6f75 7263 6546 696c 6501 000e ...SourceFile...
00000090: 5465 7374 436c 6173 732e 6a61 7661 0c00 TestClass.java..
000000a0: 0700 080c 0005 0006 0100 0954 6573 7443 ...........TestC
000000b0: 6c61 7373 0100 106a 6176 612f 6c61 6e67 lass...java/lang
000000c0: 2f4f 626a 6563 7400 2100 0300 0400 0000 /Object.!.......
000000d0: 0100 0200 0500 0600 0000 0200 0100 0700 ................
000000e0: 0800 0100 0900 0000 2f00 0100 0100 0000 ......../.......
000000f0: 052a b700 01b1 0000 0002 000a 0000 0006 .*..............
00000100: 0001 0000 0001 000b 0000 000c 0001 0000 ................
00000110: 0005 000c 000d 0000 0001 000e 000f 0001 ................
00000120: 0009 0000 0036 0004 0001 0000 000c 2a59 .....6........*Y
00000130: b400 025a 0460 b500 02ac 0000 0002 000a ...Z.`..........
00000140: 0000 0006 0001 0000 0004 000b 0000 000c ................
00000150: 0001 0000 000c 000c 000d 0000 0001 0010 ................
00000160: 0000 0002 0011
Class头
前面4个字节是固定的cafe babe
,标识这是一个class文件,接下来的6个字节和java版本相关0000 0034
,版本52 对应jdk1.8中的一个版本。
常量区
接下来的0016
,代表常量池中有21个常量(从1开始,0保留).常量类型一共有12种,他们的结构都是不一样的,在class文件中紧致排列,十二种类型结构如图(摘自<<深入理解java虚拟机>>):
下面根据上图解析class文件常量区:
1.指向方法的引用(二进制为5个字节:0a 0004 0012),内容有两个(1)指向类描述符CONSTANT_Class_info的索引项(第四项),(2)指向名称及类型描述符CONSTANT_NameAndType的索引项(第18项)。
2.CONSTANT_Fieldref_info 指向类成员变量的引用(二进制为5个字节:09 0003 0013
)内容有两个(1)指向所属的类或者接口的对象(2)指向
3.CONSTANT_Class_info (二进制为3个字节:07 0014
)对应20号常量区TestClass
4.CONSTANT_Class_info(二进制为3个字节:07 0015
)对应21号常量区java/lang/Object
5.UTF-8常量(二进制为变长结构: 1字节+标识长度的2字节+length字节: 01 0001 6d
),m
6.UTF-8常量(二进制:01 0001 49
), I
7.UTF-8常量(二进制:01 0006 3c69 6e69 743e
), <init>
8.UTF-8常量(二进制:01 0003 2829 56
), ()V
9.UTF-8常量(二进制:01 0004 436f 6465
), Code
10.UTF-8常量(二进制:01 000f 4c69 6e65 4e75 6d62 6572 5461 626c 65
),LineNumberTable
11.UTF-8常量(二进制:01 0012 4c6f 6361 6c56 6172 6961 626c 6554 6162 6c65
),LocalVariableTable
12.UTF-8常量(二进制:0004 7468 6973
),this
13.UTF-8常量(二进制:01 000b 4c54 6573 7443 6c61 7373 3b
),LTestClass
14.UTF-8常量(二进制:01 0003 696e 63
),inc
15.UTF-8常量(二进制:01 0003 2829 49
),()I
16.UTF-8常量(二进制:01 000a 536f 7572 6365 4669 6c65
),SourceFile
17.UTF-8常量(二进制:01 000e 5465 7374 436c 6173 732e 6a61 7661
),TestClass.java
18.CONSTANT_NameAndType 类型(二进制:0c 0007 0008
), 内容有两个,(1)指向字段或方法名常量项的索引,0007
:<init>
,(2)指向字段或方法名的描述符常量项索引0008
:()V
19.CONSTANT_NameAndType 类型(二进制:0c 0005 0006
), (1)m (2)I
...
类索引,父类索引和接口索引
在常量区之后,是占用两个字节的类标识,我们的类是jdk1.8生成的,所以ACC_SUPER标识是1,结合起来就是0x0021。接下来4个字节代表当前class文件的类索引和父类索引,分别为对应常量区的0003
(TestClass)和0004
(java/lang/Object),其中如果该类是顶级的Object类,那么父类索引是0。接下来两个字节是当前class实现的接口数量,在后面是接口索引.这里我们没有实现某个接口所以是0x0000
.
字段表
再接下来的 0x0001
代表类内成员变量数是1,在接下来的8个字节0002 0005 0006 0000
0002代表这个变量是声明为private的,0005
是常量区的位置,代表这个变量名是m
.如果我们代码声明中还有private int m=123
之类的,那么接下来还有一部分二进制数据是用来代表这些额外信息的。我们这里是0000
说明数量是0。无需额外空间存储信息。
方法表
接下来的00 02
代表类内方法的数量,一个是编译器自动添加的<init>
,另一个是用户定义的inc
,先看第一个0001 0007 0008 0001 0009
前两个字节代表方法的访问标识,这里具体是public
,中间的4个字节代表方法名及其返回类型:init
,返回值()V,合起来即public void init(),最后两个字节代表init
方法的属性表有1个值:再接下来4个字节是其值是0009
对应常量区Code
,Code
的结构为:
所以接下来是4个字节的长度00 00 002f
,(不包括前面的6个字节),所以值为47,max_stack=1
,max_locals=1
,code_length=5
,读入5个字节2a b700 01b1
,Code的取值可以从这里查到,查表得2a
->aload_0
,b7
->invokespecial
,invokespecial
后面跟着2个字节的常量区索引,对应取值0001
->java/lang/Object."<init>":()V
,b1
->return
,接下来的exception_info
长度为0000
,说明这个方法不抛出异常。
再接下里两个字节0002
代表属性的数量是2。读入第一个000a
查询常量区,为LineNumberTable
,查看此结构:
,length
->0000 0006
,line_number_table_length
->0001
,line_number_info
有两个2字节的数据,分别代表字节码中的行号和java代码中的行号。这里是0000
和0001
。
接下来的属性表是000b
,对应常量区LocalVariableTable
,看一下LocalVariableTable
的结构:
7.jpg
可得:attribute_length
->0000 000c
,local_variable_table_length
->0001
,local_variable_info
占10个字节,5个值分别为0->字节码起始位置,5->,12->常量区this
,13->常量区LTestClass
。我们统计一下这个方法的字节码长度,从属性表的max_stack
开始计算,正好是47个字节,和Code属性表的attribute_length
指示的值是一致的。
用同样的方法计算出inc方法区,这里忽略详细步骤。
最后的校验
最后用javap -verbose
来解析一下TestClass.class,可以看到上述流程解析出来的结果是一致的。
javap -verbose TestClass.class
Classfile /Users/jjchen/javapro/baseLearning/out/production/baseLearning/TestClass.class
Last modified 2018-10-26; size 358 bytes
MD5 checksum 0494d0eec1cf75ded4b39208b436cd6f
Compiled from "TestClass.java"
public class TestClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#18 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#19 // TestClass.m:I
#3 = Class #20 // TestClass
#4 = Class #21 // java/lang/Object
#5 = Utf8 m
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 LTestClass;
#14 = Utf8 inc
#15 = Utf8 ()I
#16 = Utf8 SourceFile
#17 = Utf8 TestClass.java
#18 = NameAndType #7:#8 // "<init>":()V
#19 = NameAndType #5:#6 // m:I
#20 = Utf8 TestClass
#21 = Utf8 java/lang/Object
{
public TestClass();
descriptor: ()V
flags: ACC_PUBLIC
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
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LTestClass;
public int inc();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=4, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field m:I
5: dup_x1
6: iconst_1
7: iadd
8: putfield #2 // Field m:I
11: ireturn
LineNumberTable:
line 4: 0
LocalVariableTable:
Start Length Slot Name Signature
0 12 0 this LTestClass;
}
网友评论