java class文件

在正式开始之前,先说下虚拟机提供的语言无关性


Java虚拟机只会解析字节码文件,至于上面到底采用的什么高级语言,他不会去关心。下面我们来看看class字节码文件的结构。

Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在class文件之中,中间没有任何分隔符。当遇到占用8位字节以上空间的数据项时,会按照高位在前的方式分割成若干个8位字节进行存储。那么class文件的存储结构如何?你怎么区分一个数据项占用了几个字节呢?class文件采用一种类似于c语言结构体的伪结构来存储,这种伪结构只有两种数据类型:无符号数与表,表是由多个无符号数或其他表作为数据项构成的复合数据项,整个class文件本质上就是一张表,class文件格式如下:

类型

名称

数量

U4

Magic

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

Attribute_info

attributes

Attributes_count

注:u1、u2、u4、u8代表1个字节、2个字节、4个字节和8个字节,以“_info”结尾表示是表。

1.魔数Magic

头4个字节,用于确定这个文件是否为一个能被虚拟机接受的class文件,该值为0xCAFEBABE,就能被虚拟机接受,否则不可以

2.次版本号Minor_version与主版本号Major_version

Java的版本号是从45开始的,JDK1.1能支持版本号为45.0~45.65535,JDK1.2能支持45.0~56.65535.下表列举了从JDK1.1到1.7之间,版本号的支持。

 


3.常量池

Constant_pool_count代表常量池容量计数值,如果Constant_pool_count=21,那么常量池  Constant_pool中就有20项常量,0项空出来。常量池中存放两大类常量:字面量和符号引用,字面量指文本字符串或final的常量值,符号引用指类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。常量池中的每一个常量都是一个表,共有11种结构各不相同的表结构,如下:


这11种常量类型均有自己的结构,如下:

(1)CONSTANT_Class_info

类型

名称

数量

U1

Tag

1

U2

name_index

1

 

Tag是标志位,name_index是一个索引值,指向常量池中一个CONSTANT_utf8_info类型的常量,指向的常量代表了类或接口的全限定名。

(2)CONSTANT_Utf8_info

类型

名称

数量

U1

Tag

1

U2

Length

1

U1

Bytes

Length

Length表示这个utf-8编码的字符串长度是多少字节,后面紧跟着的长度为Length字节的连续数据是一个使用utf-8编码的字符串。U2能表达的最大值是65535,因此java程序中如果定义超过64kB的英文字符的变量或方法名,将会无法编译。

(3)CONSTANT_Integer_info

类型

名称

数量

U1

Tag

1

U4

Bytes

1

Bytes表示按照高位在前存储的int值

CONSTANT_Float_info、CONSTANT_Long_info、CONSTANT_Double_info与CONSTANT_Integer_info类似。

 

4.访问标志 Access_flags



5.类索引this_class、父类索引super_class与接口索引Interfaces

Class文件中由这三项数据来确定这个类的继承关系,类索引用于确定这个类的全限定名、父类索引用于确定这个类的父类的全限定名。类索引与父类索引各自指向一个类型为CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info的name_index找到定义在CONSTANT_utf8_info类型的常量中的全限定名字符串。

6.字段表集合Fields_info

字段表集合Fields_info 用于描述接口或类中声明的变量,字段Fields包括类级变量或实例级变量,不包括在方法内部声明的变量。字段表结构如下:


字段修饰符放在access_flags项目中,与类中的access_flag项目十分类似。Name_index、descriptor_index,都是对常量池的引用,代表字段的简单名称及字段和方法的描述符。描述符的作用是用来描述字段的数据类型、方法的参数列表和返回值。如代码

Private Int m;

Public voidmin();

其中m字段与inc()方法的简单名称分别是m与inc,描述符为I、()V。

7.方法表集合Method_info

结构如下



与字段表集合类似。方法里的代码经过编译器编译成字节码指令之后,存放在方法属性表集合中一个名为code的属性里面。

8.属性表集合Attribute_info

在class文件、字段表、方法表都可以携带自己的属性表集合。Java虚拟机规范预定义了9项虚拟机实现应当能识别的属性。具体如下

 



对于每一个属性,它的名称需要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,属性值的结构完全自定义,只需要说明属性值所占用的位数长度即可,符合规则的属性表结构如下:


(1)Code属性

Code属性出现在方法表的属性集合中,接口或抽象类中的方法不存在code属性。Code属性表结构如下

类型

名称

数量

说明

U2

Attribute_name_index

1

指向CONSTANT_Utf8_info型常量的索引,值为code

U4

Attribute_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_length

异常表,实现java异常及finally处理机制

U2

Attributes_count

1

 

Attribute_info

attributes

Attributes_count

 

 

(2)Exceptions属性

列举出方法中可能抛出的异常,即方法描述时在throws关键字后面列举的异常,结构如下

类型

名称

数量

说明

U2

Attribute_name_index

1

 

U4

Attribute_length

1

 

U2

Number_of_exceptions

1

异常数目

U2

Exception_index_table

Number_of_exceptions

 

(3)LineNumberTable属性

描述java源码行号与字节码行号之间的对应关系,结构如下

类型

名称

数量

说明

U2

Attribute_name_index

1

 

U4

Attribute_length

1

 

U2

Line_number_table_length

1

 

Line_number_info

Line_number_table

Line_number_table_length

包括start_pc和line_number,前者是字节码行号,后者是java源码行号

(4)LocalVariableTable属性

描述栈帧中局部变量表中的变量与java源代码中定义的变量之间的关系。

(5)SourceFile

记录生成class文件的源码文件名称。

(6)ConstantValue属性

作用是通知虚拟机自动为静态变量赋值。只有被static关键字修饰的变量才可以使用这项属性。程序中

Int x=123;与static intx=123;虚拟机对其赋值方式和时刻都不一样。

(7)InnerClasses属性

记录内部类与宿主类之间的关联。如果一个类中定义了内部类,那编译器将会为它及它包含的内部类生成InnerClasses属性。

(8)Deprecated及Synthetic属性

Deprecated标志某个类、字段、方法已经被程序标志不再使用,在代码中使用@ deprecated注释进行设置。

Synthetic代表此字段或方法不是由Java源代码直接产生的,而是由编译器自行添加的。

9.举例实战

代码实例

Package org.fenixsoft.clazz

Public class TestClass{

      Private int m;

      Public int inc(){

          Return m+1;

}

}

使用WinHex打开class文件如下


魔数与版本号

前四个字节为魔数,紧接着的四个字节是版本号,0x00000033,转为十进制为51,说明该版本时可以被JDK1.7的虚拟机执行的class文件。

常量池

紧接着两个字节0x0016,转换成十进制为22,代表常量池中有21个常量,索引值为1~21。常量池中共11中常量类型,但每一种类型的第一个字节都是一个标志位字节。

第一个常量,标志位字节为0x07,十进制为7,表示CONSTANT_Class_info,通过前面的讲解,知道CONSTANT_Class_info类型的常量,后面是一个2字节的name_index,指向常量池中的CONSTANT_Utf8_info,2个字节为0x00002,指向常量池中第二项常量,

第二项常量的的标志位字节为0x01,表示是一个CONSTANT_Utf8_info类型的常量,后面两个字节代表长度,0x001D,换成十进制为29,即后面有29个字节的连续字符串。向后数29个,分别为



一个字节的Utf-8编码,相当于1~127的ASCII码,0x6F换成十进制为111,查ASCII码表字符为o,类似,往后推算,29个连续字符为org/fenixsoft/clazz/TestClass。

第三项常量的标志位为0x07,表示CONSTANT_Class_info,后面是一个2字节的name_index,值为0x0004,指向常量池中第四项常量。

第四项常量的标志位为01,表示是一个CONSTANT_Utf8_info类型的常量,后面两个字节代表0x0010,换成十进制为29,即后面有16个字节的连续字符串。向后数16个,分别为



为java/lang/Object。

第五项常量的标志为01,表示是一个CONSTANT_Utf8_info类型的常量,后面两个字节代表0x0001,换成十进制为1,即后面有1个字节的连续字符串。代表m

第六项常量的标志为01,表示是一个CONSTANT_Utf8_info类型的常量,后面两个字节代表0x0001,换成十进制为1,即后面有1个字节的连续字符串。代表I。

第七项常量的标志为01,表示是一个CONSTANT_Utf8_info类型的常量,后面两个字节代表0x0006,换成十进制为6,即后面有6个字节的连续字符串。代表<init>

第八项为()V

第九项为Code

第十项常量的标志位为0x0A,表示CONSTANT_Methodref_info,后面是一个2字节的index,值为0x0003,指向声明方法的类描述符CONSTANT_Class_info的索引项。接下来的2字节index为0x000B,指向名称及类型描述符CONSTANT_NameAndType_info的索引项。即为java/lang/Object.”<init>”.()V

第十一项常量的标志位为0x0C,表示CONSTANT_NameAndType,后面是一个2字节的index,值为0x0007,指向该字段或方法名称常量项的索引。接下来的2字节index为0x0008,指向该字段或方法描述符常量项的索引。即为”<init>”.()V.

一次类推,

第十二项为LineNumberTable

第十三项为LocalVariableTable

第十四项为this

第十五项为Lorg/fenixsoft/clazz/TestClass;

第十六项为inc

第十七项为()I

第十八项为org/fenixsoft/clazz/TestClass.m:I

第十九项为m:I

第二十项为SourceFile

第二十一项为TestClass.java

访问标志

常量池后的两个字节,值为0x0021,为ACC_PUBLIC与ACC_SUPER

类索引、父类索引、接口索引

值分别为0x0001、0x0003、0x0000,表示类索引为1,父类索引为3,接口索引集合大小为0.常量池中,第一项的值为org/fenixsoft/clazz/TestClass,第三项的值为java/lang/Object,即类为org/fenixsoft/clazz/TestClass,父类为java/lang/Object,没有接口

 

字段

接下来的两个字节0x0001,代表字段的数量为1,根据字段表的结构,后面的几项分别为

访问标志0x0002,表示ACC_PRIVATE

Name_index:0x0005,表示m

Descriptor_index:0x0006,表示I,即int

Attributes_count:0x0000,属性个数为0



即一个字段,为privateint m

 

方法

接下来的两个字节0x0002,代表方法的数量为2,根据方法表的结构,后面的几项分别为

第一个方法

访问标志0x0001,表示ACC_PUBLIC

Name_index:0x0007,表示<init>

Descriptor_index:0x0008,表示()V,即void()

Attributes_count:0x0001,属性个数为1

根据属性结构

Attribute_name_index:0x0009,为Code

Attribute_length:0x0000,长度为0

 

第二个方法是父类是父类方法,如果在子类中没有被重写,方法表集合中就不会出现父类方法的信息,因此,看不到第二个方法<clinit>



郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。