深入理解JVM:类文件结构

不羁岁月 提交于 2020-01-31 15:17:45

一、

在java诞生的时候就有一个口号:“一次执行,到处运行!”。字节码实现了这部分功能。

注意:JVM不和包括java在内的任何语言绑定,它只与Class文件这种特定的二进制文件格式所关联。

其他语言可以通过编译器生成字节码文件,然后再jvm上面运行。

javaèææº

二、Class文件

任何一个Class文件都对应这唯一一个类或者接口的定义信息,但反过说不正确。

Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照紧凑地排列再Class文件之中,中间没有增加任何间隔符。

Class文件格式采用一种类似于C语言结构体的伪结构体存储数据,这种伪结构体只有两种数据类型:无符号数和表

无符号数属于基本数据类型,以u1、u2、u4、u8来表示1到8字节的无符号数。

表是由多个无符号数或者其他表作为数据项构成的复合数据类型。

整个Class文件本质上就是一张表,习惯上以“_info”结尾。

ClassFile {
    u4             magic; //Class 文件的标志
    u2             minor_version;//Class 的小版本号
    u2             major_version;//Class 的大版本号
    u2             constant_pool_count;//常量池的数量
    cp_info        constant_pool[constant_pool_count-1];//常量池
    u2             access_flags;//Class 的访问标记
    u2             this_class;//当前类
    u2             super_class;//父类
    u2             interfaces_count;//接口
    u2             interfaces[interfaces_count];//一个类可以实现多个接口
    u2             fields_count;//Class 文件的字段属性
    field_info     fields[fields_count];//一个类会可以有个字段
    u2             methods_count;//Class 文件的方法数量
    method_info    methods[methods_count];//一个类可以有个多个方法
    u2             attributes_count;//此类的属性表中的属性数
    attribute_info attributes[attributes_count];//属性表集合
}

ç±»æ件å­èç ç»æç»ç»ç¤ºæå¾

1、魔数

u4             magic; //Class 文件的标志

每个Class文件前4个字节称魔数,这是识别class文件的标志,前四个字节为:0xCAFBBABE。

2、class版本

u2             minor_version;//Class 的次版本号
u2             major_version;//Class 的主版本号

紧接着4个字节就是版本号,5、6字节是次版本号,7、8字节是主版本号。

java版本号由45(主版本号)开始,jdk1.1后,每一次+1。

(jdk1.0 - jdk1.1 使用45.0 - 45.3),jdk1.1可支持45.0 - 45.65535

而jdk1.2则支持45.0 - 46.65535。jdk向下兼容。

3、常量池

u2             constant_pool_count;//常量池的数量
cp_info        constant_pool[constant_pool_count-1];//常量池

紧接着主次版本号之后的是常量池,常量池的数量是 constant_pool_count-1(常量池计数器是从1开始计数的,将第0项常量空出来是有特殊考虑的,索引值为0代表“不引用任何一个常量池项”)。

常量池可以理解为class文件中的资源仓库,它是文件结构中与其他项目关联最多的数据类型。

常量池主要存放两大常量:字面量和符号引用。字面量比较接近于 Java 语言层面的的常量概念,如文本字符串、声明为 final 的常量值等。而符号引用则属于编译原理方面的概念。包括下面三类常量:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。

常量池中每一项常量都是一个表,这14种表有一个共同的特点:开始的第一位是一个 u1 类型的标志位 -tag 来标识常量的类型,代表当前这个常量属于哪种常量类型.

类型 标志(tag) 描述
CONSTANT_utf8_info 1 UTF-8编码的字符串
CONSTANT_Integer_info 3 整形字面量
CONSTANT_Float_info 4 浮点型字面量
CONSTANT_Long_info 长整型字面量
CONSTANT_Double_info 双精度浮点型字面量
CONSTANT_Class_info 类或接口的符号引用
CONSTANT_String_info 字符串类型字面量
CONSTANT_Fieldref_info 字段的符号引用
CONSTANT_Methodref_info 10 类中方法的符号引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的符号引用
CONSTANT_MothodType_info 16 标志方法类型
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

4、访问标志

在常量池结束之后,紧接着的两个字节代表访问标志,这个标志用于识别一些类或者接口层次的访问信息,包括:这个 Class 是类还是接口,是否为 public 或者 abstract 类型,如果是类的话是否声明为 final 等等。

类访é®åå±æ§ä¿®é¥°ç¬¦

5、类索引、父类索引与接口索引集合

u2             this_class;//当前类
u2             super_class;//父类
u2             interfaces_count;//接口
u2             interfaces[interfaces_count];//一个雷可以实现多个接口

类索引、父类索引都是一个u2类型的数据,而接口索引是一组u2类型的集合,

类索引和父类索引各自指向了一个类型为CONSTANT_Class_info描述符常量,通过Class常量中索引值可以找到全限定类名字符串。

类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名,由于 Java 语言的单继承,所以父类索引只有一个,除了 java.lang.Object 之外,所有的 java 类都有父类,因此除了 java.lang.Object 外,所有 Java 类的父类索引都不为 0。

接口索引集合用来描述这个类实现了那些接口,这些被实现的接口将按implents(如果这个类本身是接口的话则是extends) 后的接口顺序从左到右排列在接口索引集合中。

6、字段表的集合

u2             fields_count;//Class 文件的字段的个数
field_info     fields[fields_count];//一个类会可以有个字段

字段表:用于描述接口或者类中声明的变量,但不包括局部变量。

字段表结构
类型 名称 数量 描述
u2 access_flags 1 这个描述的是字段访问标志
u2 name_index 1 简单名称,引用常量池常量。
u2 description_index 1 用来描述字段数据类型、方法参数列表(包括数量、类型以及顺序)和返回值
u2 attributes_count 1 计数器,对于普通字段都是0(常量除外)
attribute_info attributes attributes_count 用于存储一些额外的信息

access_flags取值:

å­æ®µçaccess_flagsçåå¼

注意:在java语言中字段是无法重载的,两个字段的数据类型、修饰符是否相同都必须使用不同的名称,但对于字节码来说,如果两个字段描述符不一致,那字段重名就合法。

7、方法表的集合

方法表的结构几乎与字段表的结构相似,仅在访问标志和属性表集合的可选项中有区别。

注意:在java语言中,要重载一个方法,除了要与原方法具有相同名称之外,还要求必须拥有一个与原方法不同的特征签名,这个特征签名就是一个方法中各个参数在常量池中字段的符号引用的集合,返回值不在其中,所以java语言不可以仅依靠返回值的不同对一个已有方法重载。
 

u2             methods_count;//Class 文件的方法的数量
method_info    methods[methods_count];//一个类可以有个多个方法

æ¹æ³è¡¨ç access_flag åå¼
8、属性表

u2             attributes_count;//此类的属性表中的属性数
attribute_info attributes[attributes_count];//属性表集合

在 Class 文件,字段表,方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息。与 Class 文件中其它的数据项目要求的顺序、长度和内容不同,属性表集合的限制稍微宽松一些,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写 入自己定义的属性信息,Java 虚拟机运行时会忽略掉它不认识的属性。

code属性:

java程序方法体中的代码经过javac编译处理后,最终变为字节码指令存储在code属性内。

code属性表结构
类型 名称 数量 描述
u2 attribute_name_index 1 指向字符串常量索引,code为“code”
u4 attribute_length 1 指向属性值长度
u2 max_stack 1 代表操作数栈最大深度,jvm根据这个值分配栈帧中操作数栈深度。
u2 max_locals 1 局部变量表所存储的空间。其中slot为局部变量分配内存所使用最小单位,对于...等不超过32位的数据类型用一个slot,对于64位的数据类型则需要两个slot来存放。
u4 code_length 1 代表字节码长度。jvm中规定一个方法不允许超过65535条字节码指令,虽然是一个u4类型长度值,但实际上只用了u2长度。
u1 code ·code_length 用于存放字节码指令的一系列字节流。
u2 exception_table_length 1  
exception_info exception_table exception_table_length  
u2 attributes_count 1  
attribute_info attributes attributes_count  

局部变量表:方法参数(包括实例中隐藏参数this)、显示异常处理参数(就是try-catch语句中catch定义的异常)、方法体中定义的局部变量。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!