深入JVM内核(九)

JAVA 2015-12-23

下面我们来分析一下java程序中的Class文件结构。 我们知道,一个java项目会在在bin目录下编译生成.class文件并交由JVM运行,但是.class文件不仅可以由java代码生成,ruby脚本,Groovy代码以及其他JVM语言 也可以生成.class文件交由JVM运行。 用UltraEdit可以打开二进制文件,我们用它打开一个.class文件看看:

1.png

它的文件结构非别包含:魔数,版本,常量池,访问符,类、超类、接口,字段,方法,属性。下面我们逐一介绍。

由上图可知.class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部都是程序运行的必要数据,没有空隙存在。当遇到需要占用8位字节以上的空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。 Class文件格式采用一种类似于C语言结构体的伪结构来存储,这种伪结构中只有两种数据类型:无符号数和表。 什么是无符号数?无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节、8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值,或者按照UTF-8编码构成字符串值。 表则是由多个无符号数或者其它表作为数据项构成的复合数据类型,所有表都习惯性地以"_info"结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表。 它由下表所示的数据项构成。

2.png

1.魔数(magic)

每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用是用于确定这个文件是否为一个能被虚拟机接受的Class文件。很多文件存储标准中都使用魔数来进行身份识别,譬如图片格式,如gif或jpg等在文件头中都存有魔数。Class文件魔数的值为0xCAFEBABE。如果一个文件不是以0xCAFEBABE开头,那它就肯定不是Java class文件。

2.版本号

版本号分为两种,分别为minor_version u2和major_version u2(主版本号和次版本号)

3.png

补充:Java的版本号是人45开始的,JDK1.1之后的每个JDK大版本发布主版本号向上加1,高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件,即使文件格式并未发生变化。JDK1.1能支持版本号为45.0~45.65535的Class文件,JDK1.2则能支持45.0~46.65535的Class文件。JDK1.7可生成的Class文件主版本号的最大值为51.0。

3.常量池 常量池是class文件中最为重要的部分,因为其它部分都要引用到它。 常量池用于存放字面量和符号引用,即constant_pool_count u2和constant_pool cp_info,其常量类型共12种:

11.png

下面我们逐一来看: 1.CONSTANT_Utf8 类型,包含桑部分tag 1(标识符),length u2(长度2字节),bytes[length](存储内容) 2.CONSTANT_Integer 类型,代表整型,包含 tag 3,byte u4 3.CONSTANT_String 类型,代表字符串型,包含tag 8,string_index u2 (指向utf8的索引) 4.CONSTANT_NameAndType类型,包含tag 12,name_index u2 (名字,指向utf8),descriptor_index u2 (描述符类型,指向utf8) 5.CONSTANT_Class类型,包含tag 7,name_index u2 (名字,指向utf8) 6.CONSTANT_Fieldref ,CONSTANT_Methodref ,CONSTANT_InterfaceMethodref类型,tag 分别是是9 ,10, 11,都包含class_index u2 (指向CONSTANT_Class)以及name_and_type_index u2 (指向CONSTANT_NameAndType)

4.access flag u2:类的标示符

4.png

5.this_class u2 指向常量池的Class

6.super_class u2 指向常量池的Class

7.interface_count u2 接口数量

8.interfaces interface_count个 interface u2,并且每个interface是指向CONSTANT_Class的索引

9.field_count 字段数量

10.fields field_count个field_info

11.field 包含access_flags u2 ,name_index u2 ,descriptor_index u2 ,attributes_count u2 ,attribute_info attributes[attributes_count]; 下面我们详细解释: 1.access_flags 类型标志

5.png

2.name_index u2 常量池引用 ,表示字段的名字

3.descriptor_index 表示字段的类型,分别是

  • B byte
    • C char
    • D double
    • F float
    • I int
    • J long
    • S short
    • Z boolean
    • V void
    • L 对象 Ljava/lang/Object
    • '[' 数组 [[Ljava/lang/String; = String[][]

12.method

methods_count 方法数量 methods 包含methods_count个method_info method_info,包含access_flags u2 ,name_index u2 ,descriptor_index u2 ,attributes_count u2 , attribute_info attributes[attributes_count];

其中access flag包含如下

6.png

name_index u2 方法名字,常量池UTF-8索引

descriptor_index u2 描述符,用于表达方法的参数和返回值

方法描述符有 void inc() ()V ;void setId(int) (I)V ; int indexOf(char[],int ) ([CI)I

13.attribute

在field和method中,可以有若干个attribute,类文件也有attribute,用于描述一些额外的信息,比如attribute_name_index u2 (名字,指向常量池UTF-8) attribute_length u4 (长度) info[attribute_length] u1 (内容)

注意:attribute本身也可以包含其他attribute,并且随着JDK的发展不断有新的attribute加入。

下面我们来看:

7.png

  • Deprecated,包含attribute_name_index u2和attribute_length u4

  • attribute_name_index,指向包含Deprecated的UTF-8常量

  • attribute_length,为0

  • ConstantValue,包含attribute_name_index u2,attribute_length u4,constantvalue_index u2

  • attribute_name_index,包含ConstantantValue字面量的UTF-8索引

  • attribute_length 为2

  • constantvalue_index 常量值,指向常量池,可以是UTF-8,Float, Double 等

  • Code

8.png

  • LineNumberTable - Code属性的属性

    9.png

  • LocalVariableTable - Code属性的属性

    10.png

  • Exceptions属性,和Code属性平级,表示方法抛出的异常(不是try catch部分,而是 throws部分),结构:attribute_name_index u2 ,attribute_length u4 ,number_of_exceptions u2 ,exception_index_table[number_of_exceptions] u2 (指向Constant_Class的索引)

  • SourceFile,描述生成Class文件的源码文件名称,结构:attribute_name_index u2 ,attribute_length u4(固定为2),soucefile_index u2(UTF-8常量索引)

下面我们看一个例子作为总结:

public class User {
    private int id;
    private String name;
    private int age;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

}

代码对应的class文件分析:

12.png

13.png

14.png

15.png


本文由 Tony 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

如果对您有用,您的支持将鼓励我继续创作!