深入理解JVM系列一:CLASS文件格式解析

 

先写一段代码然后编译成class,直接对照16进制码阅读:

1 package test;

2 public class test {

3         private int m;

4

5         public int inc() {

6                 return m + 1;

7         }

8 }

用winhex打开编译出的class后可以看到

clip_image002

CLASS文件的总体格式如下结构

ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}

来自 <https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1>

我们一点一点解读:

1. 开头4字节是javaclass标志性的magicnumber,CAFEBABE

2. 第5和6字节代表次版本号,一般都是0000

3. 第7-8字节表示的是主板本号,值为0x34即52,表示编译器版本是jdk8

4. 8-9字节是constant_pool入口,0x13即constant_pool_count表示了常量池中包含常量的数目是18=constant_pool_count-1

5. 接下来是每个常量池项的具体信息,常量池主要存放两大类常量:Literal 和 Symbolic Reference,Literal表示一些字符串常量以及声明为final的常量等,Symblic Reference则表示类和接口名、字段名、方法名等。

偏移为0xA位置的值为0xA,通过查表(1)可得这是一个CONSTANT_Methodref(字段),字段结构如下:

CONSTANT_Methodref_info {

           u1 tag;

           u2 class_index;

           u2 name_and_type_index;

}

tag之后是class_index值为0x0004,class_index 项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Class_info结构,表示一个类或接口,当前字段或方法是这个类或接口的成员,值得注意的是如果一个 CONSTANT_Methodref_info 结构的方法名以“<”('\u003c')开头,说明这个方法名是特殊的,即这个方法是实例初始化方法。接着就是name_and_type_index,值为0x000f,name_and_type_index 项的值必须是对常量池的有效索引,常量池在该索引处的项必须是 CONSTANT_NameAndType_info结构,它表示当前字段或方法的名字和描述符。

偏移为0xF位置的值为0x9,是一个CONSTANT_Fieldref和CONSTANT_Methodref有一样的结构,详细说明参见:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1

偏移为0x14位置的值为0x7是一个CONSTANT_Class,我们跳过,偏移为0x17位置的值为0x7也是一个CONSTANT_Class,并且他就是最开始CONSTANT_Methodref第二个字段所指向的。

Constant Type

Value

CONSTANT_Class

7

CONSTANT_Fieldref

9

CONSTANT_Methodref

10

CONSTANT_InterfaceMethodref

11

CONSTANT_String

8

CONSTANT_Integer

3

CONSTANT_Float

4

CONSTANT_Long

5

CONSTANT_Double

6

CONSTANT_NameAndType

12

CONSTANT_Utf8

1

CONSTANT_MethodHandle

15

CONSTANT_MethodType

16

CONSTANT_InvokeDynamic

18

表(1)

来自 <https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1>

当然大可不必这么麻烦,javap可以轻松输出这些分析javap -verbose test.class:

clip_image004

从这里可以看出java所有类会继承java.lang.object,编译器会为没有显示构造函数的类做出一个默认的构造函数出来,在创建对象时,先调用父类默认构造函数对对象进行初始化。()V应该就是void的意思,I即int。

6. 接下来是access_flags位于0x9B,可以看到constant_pool占了很大一部分空间,access_flags值为0x0021,从表(2)看出这是一个public类,然后还有ACC_SUPER属性,在 JDK 1.0.2 之后编译出的 Class 文件,都带有 ACC_SUPER 标志用以和以前版本区分。这里说明一下invokespecial指令,每个类都至少会有一个<init>()方法,这些方法通常是用invokespecial调用。

Flag Name

Value

Interpretation

ACC_PUBLIC

0x0001

Declared public; may be accessed from outside its package.

ACC_FINAL

0x0010

Declared final; no subclasses allowed.

ACC_SUPER

0x0020

Treat superclass methods specially when invoked by the invokespecial instruction.

ACC_INTERFACE

0x0200

Is an interface, not a class.

ACC_ABSTRACT

0x0400

Declared abstract; must not be instantiated.

ACC_SYNTHETIC

0x1000

Declared synthetic; not present in the source code.

ACC_ANNOTATION

0x2000

Declared as an annotation type.

ACC_ENUM

0x4000

Declared as an enum type.

表(2Class access and property modifiers

来自 <https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1>

7. 然后是this_class,值为0x0003是对constant_pool表中项目的一个有效索引值,从字面意思就是当前类了。

8. 之后是super_class,值为0x0004,即java.lang.object,如果 Class 文件的 super_class 的值为 0,那这个 Class 文件只可能是定义的是 java.lang.Object 类,只有它是唯一没有父类的类。

9. Interface_count表示当前类或接口的直接父接口数量,值为0即没有。

10. interfaces[]字段直接就没有了,可以看出class文件的紧凑。

11. 位于0xA2处的0x0001是field_count,表示当前 Class 文件 fields[]数组的成员个数。

12. 之后就是fieldtable的一个项了,filedtable项的结构为:

field_info {
      u2 access_flags;
      u2 name_index;
      u2 descriptor_index;
      u2 attributes_count;
attribute_info attributes[attributes_count];
}来自 <
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1>

   Fileld_info值依次为0x0002 0x0005 0x0006 分别表示 private m int,随后的attributes_count为0,attribute项用来表示标识符的修饰,例如如果定义private static int m;则这里就会有constantvalue项。

13. 紧随其后的0xAC位置表示method_count,值为0x0002,接下来的0x0001 0x0007 0x0008分别表示public <init> void,这个初始化函数有attribute,attribute结构为:

attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}

来自 <https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7>

attribute_name_index值为0x0009,在constant_pool中索引为code,此属性为code描述符即java代码编译器编译成的字节码指令,之后的length为0x001D。

code描述符为

Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack;
    u2 max_locals;
    u4 code_length;
    u1 code[code_length];
    u2 exception_table_length;
    { u2 start_pc;
       u2 end_pc;
       u2 handler_pc;
       u2 catch_type;
    } exception_table[exception_table_length];
       u2 attributes_count;
       attribute_info attributes[attributes_count];
}

来自 <https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3>

随后的max_stack和max_locals都为1,max_stack代表了操作数栈深度的最大值,max_local代表了局部变量表所需空间,单位为slot,长度不超过32位的数据类型占一个slot,64位的数据如double和long需要2个slot。java编译器会根据变量的作用域来分配slot给各个变量使用。

Code_length为5,紧接着的便是java指令字节码,0x2AB7000AB1对应指令为,即加载参数、初始化、返回。等等哪里来的参数呢?实际上就是this啦。

clip_image006

接下来是异常处理,也是有点复杂,就跳过了,直接进入下一个方法入口位于0xD9处,分析同上。

直到0x106,两个方法结束。

14. 接着一个属性从0x106开始,name_index=0xD,为sourcefile,其格式为:

SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}

来自 <https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3>

对应的值为test.java即文件名。

 

posted @ 2015-06-18 05:18  liujshi  阅读(436)  评论(0编辑  收藏  举报
MathJax.Hub.Config({ jax: ["input/TeX","output/HTML-CSS"], displayAlign: "left" });