JVM原理笔记系列二:Class文件的格式

Class文件是一组以8bit为基础的二进制流,各个数据项严格按照顺序紧凑地排列在Class文件中,中间没有添加任何分隔符。当遇到需要占用8位字节以上空间的数据时,按照高位在前的方式进行存储。

根据JVM规范规定,Class文件采用一种类似于C语言结构体的形式来存储,这种伪结构中只有两种数据类型:无符号数和表。

无符合号数属于基本数据类型,以u1、u2、u3、u4分别代表1个字节、2个字节、3个字节和4个字节的无符号数。无符号数可以用于描述数字、索引引用、数量值或按照UTF-8编码构成字符串值。

表是由多个无符号数或其他表为数据项构成的复合数据类型。习惯上,所有表都以_info续表。表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表。

表格1 Class文件格式

类型

名称

数量

u4

magic number

1

u2

minor_version

1

u2

major_version

1

u2

constant_pool_count

1

cp_info

constant_pool

constant_pool_count-1

u2

access_flags

1

u2

this_class

1

u2

super_class

1

u2

interfaces_count

1

u2

interfaces

interfaces_count

u2

fields_count

1

field_info

fields

fields_count

u2

methods_count

1

method_info

methods

methods_count

u2

attributes_count

1

acctribute_info

attributes

attributes_count

我们以一个简单的例子来说明一下Class文件的格式,源代码为:

interface A

{

public void sayA();

}

interface B

{

public void sayB();

}

public class ClassFileDemo implements A, B

{

public static void main(String[] args)

{

ClassFileDemo demo = new ClassFileDemo();

demo.getNumberA();

demo.getStringB();

}

public int getNumberA()

{

return a;

}

public String getStringB()

{

return b;

}

public void sayA()

{

}

public void sayB()

{

}

private int a = 0;

private String b = “”;

}

编译后的class文件如下图所示:

《JVM原理笔记系列二:Class文件的格式》

《JVM原理笔记系列二:Class文件的格式》

 

文件的头四个字节为magic number,固定为CA FE BA BE,其主要作用为确定该文件是否为一个能被虚拟机接受的Class文件。从第五到第八个字节,共四个字节,存储的是Class文件的版本号;前字节为次版本号,后两个字节为主版本号。Java版本号从45开始,从JDK1.1之后每发布一个大版本,主版本号就向上加1(JDK1.0-1.1使用了45.0-45.3的版本号)。高版本的JDK能够向下兼容以前版本的Class文件,但不能运行之后版本的Class文件。

从第九个字节开始,就进入到class文件的常量池了。第九、十两个字节,表示常量池条目的数量,00 28换算成十进制就是40,代表常量池中有39个条目[1]。常量池中主要存储两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量概念比较接近接近Java语言层面的常量概念,如字符串、被声明为final的常量值等。符号引用主要包含类、接口的全限定名、字段名称和描述符、方法名称和描述符三大类内容。

常量池的每一项常量都是一个表,共有11种不同的表结构。这些表结构的第一位是一个u1类型的标识位,取值为1-2(缺少2的数据类型),标识当前常量属于哪种类型,其具体含义如下表所示:

表格 常量池的项目类型

类型

标志

描述

CONSTANT_Utf8_info

1

UTF-8编码的字符串

CONSTANT_Integer_info

3

整型字面量

CONSTANT_Float_info

4

浮点型字面量

CONSTANT_Long_info

5

长整型字面量

CONSTANT_Double_info

6

双精度浮点型字面量

CONSTANT_Class_info

7

类或接口的符号引用

CONSTANT_String_info

8

字符串类型字面量

CONSTANT_Fieldref_info

9

字段的符号引用

CONSTANT_Methodref_info

10

类中方法的符号引用

CONSTANT_InterfaceMethodref_info

11

接口中方法的符号引用

CONSTANT_NameAndType_info

12

字段或方法的部分符号引用

回到具体的例子上来,偏移量为0000000A地址上,我们可以看到值为0A,十进制数为10,即第一项为CONSTANT_Methodref_info类型。我们从“常量池中的11种数据类型的结构总表”中得知,该类型共包含5个字节,0A 00 09 00 1E,0009指向常量池的第9项,001E指向常量池的第30项。常量池第一项解析完毕。

第二项:标志位为09,该类型为CONSTANT_Fieldref_info,共占有5个字节,09 00 05 00 1F,00 09指向常量池的第5项,00 1F指向常量池的第31项。

第三项:标志位为08,该类型为CONSTANT_String_info,后面紧跟的两个字节00 20,指向常量池中的第32项。

第四项:标志位为09,类型同第二项,共占用5个字节,09 00 05 00 21,00 05指向常量池中的第五项,00 21指向常量池中的第33项。

第五项:标志位为07,类型为CONSTANT_Class_info,占用3个字节,00 22指向常量池中的第34项。

第六项:标志位为0A,类型同常量池第一项,占用5个字节 0A 00 05 00 1E,其中00 05指向常量池中第5项,00 1E指向常量池中第30项。

第七项:标志位为0A,类型同第六项,占用5个字节0A 00 05 00 23,其中00 05指向常量池中第5项,00 23指向常量池中第35项。

第八项:标志位为0A,类型同第七项,占用5个字节0A 00 05 00 24,其中00 05指向常量池中第5项,00 24指向常量池中第36项。

第九项:标志位为07,类型为CONSTANT_Class_info,占用3个字节07 00 25,其中00 25指向常量池中第37项。

第十项:标志位为07,类型为CONSTANT_Class_info,占用3个字节07 00 26,其中00 26指向常量池中第38项。

第十一项:标志位为07,类型为CONSTANT_Class_info,占用3个字节07 00 27,其中00 27指向常量池中第39项。

第十二项:标志位为01,类型为CONSTANT_Utf8_info,占用4个字节 01 00 01 61,其中长度为00 01,值为61,显示为a。

第十三项:标志位为01,类型为CONSTANT_Utf8_info,占用4个字节 01 00 01 49,其中长度为00 01,值为49,显示为I。

第十四项:标志位为01,类型为CONSTANT_Utf8_info,占用4个字节 01 00 01 62,其中长度为00 01,值为62,显示为b。

第十五项:标志位为01,类型为CONSTANT_Utf8_info,占用21个字节,01 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b,其中长度为00 12,字符串显示为Ljava/lang/String;

第十六项:标志位为01,类型为CONSTANT_Utf8_info,占用9个字节,01 00 06 3c 69 6e 69 74 3e,其中长度为00 06,字符串显示为<init>

第十七项:标志位为01,类型同第十六项,占用6个字节,01 00 03 28 29 56,其中长度为00 03,字符串显示为()V

第十八项:标志位为01,类型同第十六项,占用7个字节,01 00 04 43 6f 64 65,其中为00 04,字符串显示为Code

第十九项:标志位为01,类型同第十六项,占用18个字节,01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65,其中长度为00 0f,字符串显示为LineNumberTable

第二十项:标志位为01,类型同第十六项,占用7个字节,01 00 04 6d 61 69 6e,其中长度为00 04,字符串显示为main

第二十一项:标志位为01,类型同第十六项,占用25个字节,01 00 16 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 29 56,其中长度为00 16,字符串显示为([Ljava/lang/String;)V

第二十二项:标志位为01,类型同第十六项,占用13个字节,01 00 0a 67 65 74 4e 75 6d 62 65 72 41,长度为00 0a,字符串显示为getNumberA。

第二十三项:标志位为01,类型同第十六项,占用6个字节,01 00 03 28 29 49,长度为00 03,字符串显示为()I

第二十四项:标志位为01,类型同第十六项,占用13个字节,01 00 0a 67 65 74 53 74 72 69 6e 67 42,长度为00 0a,字符串显示为getStringB

第二十五项:标志位为01,类型同第十六项,占用23个字节,01 00 14 28 29 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b,长度为00 14,字符串显示为()Ljava/lang/String;

第二十六项:标志位为01,类型同第十六项,占用7个字节,01 00 04 73 61 79 41,长度为00 04,字符串显示为sayA

第二十七项:标志位为01,类型同第十六项,占用7个字节,01 00 04 73 61 79 42,长度为00 04,字符串显示为sayB

第二十八项:标志位为01,类型同第十六项,占用13个字节,01 00 0a 53 6f 75 72 63 65 46 69 6c 65,长度为00 0a,字符串显示为SourceFile

第二十九项:标志位为01,类型同第十六项,占用21个字节,01 00 12 43 6c 61 73 73 46 69 6c 65 44 65 6d 6f 2e 6a 61 76 61,长度为00 12,字符串显示为ClassFileDemo.java

第三十项:标志位为0c,类型为CONSTANT_NameAndType_info,占用5个字节,0c 00 10 00 11,其中00 10指向常量池中第16项,00 11指向常量池中第17项。

第三十一项:标志位为0c,类型同第三十项,占用5个字节,0c 00 0c 00 0d,00 0c指向常量池中第12项,00 0d指向常量池中第13项。

第三十二项:标志位为01,类型同第十六项,占用3个字节,01 00 00

第三十三项:标志位为0c,类型同第三十项,占用5个字节,0c 00 0e 00 0f,00 0e指向常量池中第14项,00 0f指向常量池中第15项。

第三十四项:标志位为01,类型同第十六项,占用16个字节,01 00 0d 43 6c 61 73 73 46 69 6c 65 44 65 6d 6f 0c,长度为00 0d,字符串显示为ClassFileDemo。

第三十五项:标志位为0c,类型同第三十项,占用5个字节,0c 00 16 00 17,00 16指向常量池中第22项,00 17指向常量池中第23项。

第三十六项:标志位为0c,类型同第三十项,0c 00 18 00 19,0018指向常量池中24项,0019指向常量池中第25项。

第三十七项:标志位为01,类型同第十六型,占用19个字节,01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74,长度为0010,字符串显示为java/lang/Object

第三十八项:标志位为01,类型同第十六项,占用4个字节,01 00 01 41,长度为0001,字符串显示为A

第三十九项:标志位为01,类型同第十六项,占用个字节,01 00 01 42,长度为0001,字符串显示为B

常量池中的11种数据类型的结构总表

常量

项目

类型

描述

CONSTANT_Utf8_info

tag

u1

1

length

u2

bytes

u1

长度为length的UTF-8编码的字节串

CONSTANT_Integer_info

tag

u1

3

bytes

u4

高位在前存储的int值

CONSTANT_Float_info

tag

u1

4

bytes

u4

高位在前存储的float值

CONSTANT_Long_info

tag

u1

5

bytes

u8

高位在前存储的long值

CONSTANT_Double_info

tag

u1

6

bytes

u8

高位在前存储的double值

CONSTANT_Class_info

tag

u1

7

index

u2

指向全限定名常量的索引

CONSTANT_String_info

tag

u1

8

index

u2

指向字符串字面量的索引

CONSTANT_Fieldref_info

tag

u1

9

index

u2

指向声明字段的类或接口描述符CONSTANT_Class_Info的索引项

index

u2

指向字段描述符CONSTANT_NameAndType的索引项

CONSTANT_Methodref_info

tag

u1

10

index

u2

指向声明方法的类描述符CONSTANT_Class_Info的索引项

index

u2

指向名称及类型描述符CONSTANT_NameAndType的索引项

CONSTANT_InterfaceMethodref_info

tag

u1

11

index

u2

指向声明方法的接口描述符CONSTANT_Class_Info的索引项

index

u2

指向名称及类型描述符CONSTANT_NameAndType的索引项

CONSTANT_NameAndType_info

tag

u1

12

index

u2

指向该字段或方法名称常量项的索引

index

u2

指向该字段或方法描述符常量项的索引

常量池解析完毕之后的两个字节,代表access_flags,值为00 21。access_flags用于标识一些类或接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等。具体信息见下表所示:

标志名称

标志值

含义

ACC_PUBLIC

0x0001

是否为public类型

ACC_FINAL

0x0010

是否声明为final,只有类可设置

ACC_SUPER

0x0020

是否允许使用invokespecial字节码指令,JDK1.2之后编译出来的类这个标志为真

ACC_INTERFACE

0x0200

标识这是一个接口

ACC_ABSTRACT

0x0400

是否为abstract类型,对于接口或抽象类来说,此标志值为真,其它类值为假

ACC_SYNTHETIC

0x1000

标识这个类并非由用户代码产生的

ACC_ANNOTATION

0x2000

标识这是一个注解

ACC_ENUM

0x4000

标识这是一个枚举

本类符合ACC_PUBLIC和ACC_SUPER这两个标志,所以结果为0021。

接下来的两个字节描述this_class,0005,指向常量池中的第五项。

接着的两个字节描述super_class,0009,指向常量池中的第九项。

接着的两个字节描述实现接口的个数interfaces_count,0002,表示实现了两个接口。紧接着用四个字节来指示接口的索引,前两个字节000a指向常量池中第十项,后两个字节000b指向常量池中的第十一项。

接着开始解析字段,0002表示本类中有两个字段,字段表的描述如下表:

类型

名称

数量

u2

access_flags

1

u2

name_index

1

u2

descriptor_index

1

u2

attributes_count

1

attribute_info

attributes

attributes_count

字段的access_flags同类的access_flags含义非常相似,如下表

标志名称

标志值

含义

ACC_PUBLIC

0x0001

字段是否为public类型

ACC_PRIVATE

0x0002

字段是否为private类型

ACC_PROTECTED

0x0004

字段是否为protected类型

ACC_STATIC

0x0008

字段是否static

ACC_FINAL

0x0010

字段是否final

ACC_VOLATILE

0x0040

字段是否volatile

ACC_TRANSIENT

0x0080

字段是否transient

ACC_SYNTHETIC

0x0100

字段是否是由编译器自动产生的

ACC_ENUM

0x4000

字段是否enum

本例中access_flags为0002,表示此字段类型private,name_index为000c,表示引用常量池中的第十二项,name值为a,descriptor_index值为000d,表示引用常量池中的第十三项,其值为I。

描述符的作用是用于描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值,根据描述符规则,基本类型类型及代表无返回值的void类型都用一个大写字母来进行表示,而对象类型则用字符L加对象的全限定名来表示,详见下表

标识字符

含义

B

byte

C

char

D

double

F

float

I

int

J

long

S

short

Z

boolean

V

void

L

对象类型

对于数组类型,每一维度用一个前置的[来进行描述。

根据上述描述可得知该定做为整型数值。

接着两个节目0000表示该字段的attributes_count为0。即可以下一个字段的解析,0002为access_flags,000e为name_index,000f为descriptor_index。0000代表attributes长度为0。

至此为止,字段信息解析完毕,接下来开始解析方法信息。

0006表示有6个方法表,方法表结构描述信息如下:

类型

名称

数量

u2

access_flags

1

u2

name_index

1

u2

descriptor_index

1

u2

attributes_count

1

attribute_info

attributes

attributes_count

方法表的访问标识定义如下表:

标志名称

标志值

含义

ACC_PUBLIC

0x0001

方法是否为public类型

ACC_PRIVATE

0x0002

方法是否为private类型

ACC_PROTECTED

0x0004

方法是否为protected类型

ACC_STATIC

0x0008

方法是否static

ACC_FINAL

0x0010

方法是否final

ACC_SYNCHRONIZED

0x0020

方法是否synchronized

ACC_BRIDGE

0x0040

方法是否由编译器产生的桥接方法

ACC_VARAGS

0x0080

方法是否接受不定参数

ACC_NATIVE

0x0100

方法是否native

ACC_ABSTRACT

0x0400

方法是否abstract

ACC_STRICT

0x0800

方法是否strictfp

ACC_SYNTHETIC

0x1000

方法是否由编译器自动产生的

第一个方法表,access_flags为0001,表明该方法为public的,name_index为0010,引用常量池中第16项,名字为<init>,descriptor_index为0011,引用常量池中的第17项()V,表示无返回值,attributes_count为0001,表示有1条属性,我们接着解析属性表,虚拟机规范预定义的属性如下表所示:

属性名称

使用位置

含义

Code

方法表

Java代码编译成的字节码指令

ConstantValue

字段表

final关键字定义的常量值

Deprecated

类、方法表、字段表

被声明为deprecated的方法和字段

Exceptions

方法表

方法抛出的异常

InnerClasses

类文件

内部类列表

LineNumberTable

Code属性

Java源码的行号与字节码指令的对应关系

LocalVariableTable

Code属性

方法的局部变量描述

SourceFile

类文件

源文件名称

Synthetic

类、方法表、字段表

标识方法或字段是由编译器自动生成的

0012引用常量池中的第18项,为Code,表示该属性是Code属性。我们接着来看一下Code属性的具体定义:

Code属性表的结构

类型

名称

数量

u2

attributes_name_index

1

u4

attributes_length

1

u2

max_stack

1

u2

max_locals

1

u4

code_length

1

u1

code

code_length

u2

exception_table_length

1

exception_info

exception_table

exception_table_lengh

u2

attributes_count

1

attribute_info

attributes

attributes_count

00000030,表示attributes_length为48,max_stack为0002,max_locals为0001,code_length为00000010,16个字节,2a b7 00 01 2a 03 b5 00 02 2a 12 03 b5 00 04 b1,每个字节表示一条字节码指令,0000表示异常表长度为0,0001表示属性长度为1,继续解析属性表,0013引用第19项常量,其值为LineNumberTable,我们再来看一下LineNumberTable属性的定义情况:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

line_number_table_length

1

line_number_table_info

line_number_table_info

line_number_table_length

000000e,该属性长度为14,0003表示有3个line_number_table,line_number_table包含两个u2类型的数据,前一个表示字节码行号,后一个表示源码行号。

[1] 由于常量池索引计算是从1开始的,而不是其他程序意义上的从0开始,所以为39个条目。制定Class文件格式规范时,将第0项空出来是有特殊考虑的,这样做是为了满足后面某些指向常量池的索引值的数据在特定情况下需要表达为“不引用任何一个常量池项目”的意思,这样的情况就可以把索引位置置为0来表示。

    原文作者:JVM
    原文地址: http://www.cnblogs.com/zhaoyongwang/archive/2012/02/01/2335154.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞