您的位置:首页 > 编程语言 > Java开发

java的class文件结构

2017-07-01 00:00 204 查看
摘要: 我们对*.class文件的鼎鼎大名可谓是耳熟能详。从学java开始我就知道由java文件编译成的class字节码文件可以直接交由jvm虚拟机去执行,相当于是jvm的可执行文件。初学java时我曾好奇的使用文本编辑器打开class文件,不过也就看到一堆乱码,深深不得其意。一直到接下来我接触到了java的反编译工具,我才认识到原来class文件是可以被解码的。 随着时间的推移,我对class这堆乱码完全失去的兴趣。直到初次接触java的四年后的现在,在我研究学习jvm原理的时候又接触到了class文件,这一次我将借助手头现有的资料,将class文件的结构完全记录下来,分享出去。

Technical--Documentation

共享技术文档

项目简介

为为日常工作和学习的总结。

技术文档主站简介

主站文档是我在前人的脚步上对研发的学习和总结纪录,在以后的日子里,我会将自己研发工作中所可能遇见问题和心得在这里纪录下来,公开的分享。世界的存在很美好,开源的存在很精彩。从今天起,我便借助开源的力量,向改变人类的生活方式这条路迈进。

点击这里直接进入为为技术文档主站,或者访问 https://weiwei02.github.io/Technical--Documentation/
与文档配套的 Technical--Documentation项目的地址是 https://github.com/weiwei02/Technical--Documentation

非原创声明

本文并非我的原创文章,而是我学习jvm时的笔记。文中的材料与数据大部分来自于其它资料,详细请查看本文的引用章节。

*.class文件介绍

一般来讲*.class文件是*.java文件在编译器编译后生成的jvm能够运行的文件,*.class文件又常被称为字节码文件。java在创始之初,就提倡“一次编写,处处运行的概念”,在当今编程圈中这个概念早已不是什么特例。java通过将开发人员所编写的java代码编译成class文件,然后由jvm虚拟机在执行时将不分平台的class文件中的字节码,再翻译成机器码,交给硬件执行。java就是靠jvm虚拟机的这个设计来实现与平台无关的特性的。class文件不但与硬件平台和操作系统无关,也和具体的编程语言无关,就目前来说,如函数式编程语言scala与Groovy都可以通过自己的编译器将源代码编译成class文件,在jvm上运行。
综合来讲,class文件有以下两点特性:

与硬件和操作系统平台无关

与源码所使用的编程语言无关


3ff0
class类文件的结构

每一个class文件都唯一对应着java类或接口枚举等定义信息,但类不一定都定义在class文件中,类可能是由类加载器动态生成的。

class文件所以被称之为字节码据我猜测可能是因为class文件以8位(1字节)为单位进行存储的二进制信息。__字节码中各个数据项目严格按照顺序紧凑的排列,中间没有任何分割符。__在需要存储整型或浮点型这些大于8位的数据项目时,则会使用Big-Endian的字节序进行存储,将最高位字节放在地址最低位,最低位字节放在地址最高位。

class文件格式采用表来存储数据,表中有无符号数和表两种数据类型。对于这两种数据类型说明如下:

无符号数: 无符号数是基本的数据类型,可以用来表示数字、索引引用、数量值或者按照UTF-8编码构成的字符串。如u1,u2,u4,u8分别代表1,2,4,8个字节字节的无符号数。

: 表是由多个无符号数或者其它表作为数据项所组成的复合数据结构,表用于描述层次关系和复合的数据结构,整个class文件就是一张表。通常表以 _info 结尾。如表1就是一个class的表示例。

类型名称数量
u4magic1
u2minor_version1
u2major_version1
u2constant_pool_count1
cp_infoconstant_poolconstant_pool_count - 1
u2asscess_flags1
u2this_class1
u2super_class1
u2interfaces_count1
u2interfacesinterfaces_count
u2fields_count1
field_infofieldsfields_count
u2methods_count1
method_infomethodsmethod_count
u2attributes_count1
attribute_infoattributesattributes_count
表 1 class文件格式

class文件的顺序和格式必须严格实现按照上述规则,否则jvm将不能识别执行。

Magic Number 魔数

魔数是一个和文件后缀名相似的用于文件格式识别的约定,一般规定文件内容开头的前几个字节为文件的魔数。不同于文件后缀名很容易被用户以重命名的方式进行更改,文件的魔数作为识别手段可以更安全的确定文件的可用性。

class文件使用前4个字节作为魔数,来确定.class文件是否是一个能够被虚拟机识别的文件,其值是 0xCAFFEBABE 。

版本

class文件的第5-6个字节代表的可执行该class文件的目标虚拟机的最低次版本号(Minor Version),第7-8个字节是主版本号(Major Version)。java虚拟机可以运行比当前虚拟机版本号低的class文件,拒绝运行版本号不合法,或比自己版本高的class文件。JDK1.1的版本号是45,之后的每个大版本发布都把主版本号加1,如JDK1.2主版本号是46,JDK8的版本号是52。

JDK在编译java文件是可以通过
javac -target 1.6 ...
命令来指定编译后的class文件可以在1.6的虚拟机版本上运行。

常量池

常量池是class文件中第一个表类型的数据项目,常量池是class文件中的资源仓库,是class文件结构中与其它项目关联最多的数据类型,也是占用class文件空间最大的数据项目之一。

class文件里紧随版本之后的数据项是常量池,由于常量池的数量是不固定的,所以在常量池的数据项之前放置的有一个u2类型的数据,代表常量池的大小。常量池大小的初始值是1,如常量池的大小的数据如果显示的是10,就代表该class文件中有9个常量。

常量池中主要存放两大类常量:Literal(字面常量)、Symbolic Reference(符号引用常量)。字面常量类似于java中常量的概念,如文本字符串、final关键字所声明的常量等。而符号引用常量则是编译中的概念,主要包括以下三种类型的常量。

符号常量

类和接口的全限定名(Fully Qualified Name)

字段名称和描述符(Descriptor)

方法的名称和描述符

class文件中不会保存各个方法或字段在内存中的布局信息,而是在虚拟机加载class文件时进行动态的连接。虚拟机在运行class文件时从常量池中获取对应的符号引用,再在创建类或者运行时解析连接到具体的内存地址当中。

常量池中的每一个常量都是一个表,在JDK8中有14种表结构的常量表。如表 2 所示。

类型标识描述 
CONSTANT_utf8_info1 UTF-8编码的字符串
CONSTANT_Integer_info3整形字面量
CONSTANT_Float_info4浮点型字面量
CONSTANT_Long_info长整型字面量
CONSTANT_Double_info双精度浮点型字面量
CONSTANT_Class_info类或接口的符号引用
CONSTANT_String_info字符串类型字面量
CONSTANT_Fieldref_info字段的符号引用
CONSTANT_Methodref_info10类中方法的符号引用
CONSTANT_InterfaceMethodref_info11接口中方法的符号引用
CONSTANT_NameAndType_info12字段或方法的符号引用
CONSTANT_MothodType_info16标志方法类型
CONSTANT_MethodHandle_info15表示方法句柄
CONSTANT_InvokeDynamic_info18表示一个动态方法调用点
表 2 常量池数据项目类型表

这14种常量结构表开始的第一个字节都是一个u1类型的标识位,其值就是表2中每项常量表类型所对应的标识列的值,代表当前常量属于哪种常量类型。这14种常量类型各自有自己不同的表结构,详情如表 3 所示。

| 常量 | 项目 | 类型 | 描述
| :------: | :------: |:------:| :------:
| CONSTANT_Utf8_info | tag | u1 | 值为1
| | length | u2 | UTF-8编码的字符串占用的字节数
| | bytes | u1 | utf-8编码的字符串
|

| CONSTANT_Integer_info | tag | u1 | 值为3
| | bytes | u4 | 按照Big-Endian存储的int值
|
| CONSTANT_Float_info | tag | u1 | 4
| | bytes | u4 | 按照Big-Endian存储的float值
|
| CONSTANT_Long_info | tag | u1 | 5
| | bytes | u8 | 按照Big-Endian存储的long值
|
| CONSTANT_Double_info | tag | u1 | 6
| | bytes | u8 | 按照Big-Endian存储的long值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_info的索引值
|
| CONSTANT_Methodref_info | tag | u1 | 10
| | index | u2 | 指向声明方法的类描述符CONSTANT_Class_info的索引值
| | index | u2 | 指向CONSTANT_NameAndType_info的索引值
|
| CONSTANT_InterfaceMethodref_info| tag | u1 | 11
| | index | u2 | 指向声明方法的接口描述符CONSTANT_Class_info的索引值
| | index | u2 | 指向CONSTANT_NameAndType_info的索引值
|
| CONSTANT_NameAndType_info | tag | u1 | 12
| | index | u2 | 指向该字段或方法名称常量的索引值
| | index | u2 | 指向该字段或方法描述符常量的索引值
|
| CONSTANT_MethodHandle_info | tag | u1 | 15
| |reference_kind| u1 | 值必须1~9,它决定了方法句柄的的类型。方法句柄类型的值表示方法句柄的字节码行为
| |reference_index| u2 | 对常量池的有效索引
|
| CONSTANT_MethodType_info | tag | u1 | 16
| |description_index|u2| 对常量池中方法描述符的有效索引常量池在该处的索引必须是CONSTANT_Utf8_info的结构,表示方法的描述符。
|
| CONSTANT_InvokeDynamic_info | tag | u1 | 18
| |bootstap_method_attr_index| u2 | 对当前class文件中引导方法表的bootstap_methods[]数组的有效索引
| |name_and_type_index| u2 | 对当前常量池的有效索引,常量池在此处必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述。
表 3 常量池中14中常量的结构总表

在表 3 中可以看到class文件所支持的所有的常量的结构信息,另外由于class中的类名、方法、字段都要用CONSTANT_Utf8_info型的常量来描述名称,所以CONSTANT_Utf8_info的最大长度也是java中类名、方法名或字段的最大长度65535,如果超出这个最大长度,便会无法编译。

访问标识

在class文件中位于常量池之后的是访问权限标识。class文件的访问标识是用于修饰这个class文件所代表的的类或接口本身的一些属性。访问标识是用一个u2类型的数据来代表的,如0x0001代表public,0x0020代表这是一个接口。具体的访问标识和标志值的对应关系见表4.访问标识是按位进行标识的,一个u2类型的数据有16位,目前其中的8位都被赋予了实际意义。

| 标识名称 | 标识值 | 意义
| :------------- | :------------- |
| ACC_PUBLIC | 0X0001 | public的访问权限
| ACC_FINAL | 0x0010 | 如果该class文件代表一个类,那么该类是final修饰的(只能类才能拥有此标识)
| ACC_SUPER | 0x0020 | 使用invokespecial字节码指令的新语义
| ACC_INTERFACE | 0x0200 | 接口
| ACC_ABSTRACT | 0x0400 | abstract(只有接口和抽象类有效)
| ACC_SYNTHETIC | 0x10000 | 本类不是用户代码所产生的类
| ACC_ANNOTATION | 0x2000 | 注解
| ACC_ENUM | 0x4000 | 枚举
表4 访问标识和标志值的对应关系表

类索引

类索引是一个u2类型的数据,用于确定这个类的全限定名。其索引值指向一个类型为CONSTANT_Class_info的常量。

父类索引

父类索引指向着当前类所继承的类的父类的全限定名,其类型信息与类索引相同。所有java类都有父类索引。

接口索引

java不支持多重继承,所以只有一个父类索引。但一个java类或接口可以实现多个其它接口,所以class的接口索引是一个数量并不固定的集合。

在声明接口索引之前,class文件首先会声明一个u2类型的数据代表接口索引的总数量,其后面紧跟者就是接口索引集合,如果该类没有实现任何接口,那么接口索引的总数量的值就是0。接口索引同样也指向常量池中的CONSTANT_Class_info类型的常量,使用CONSTANT_Utf8_info的字符串常量来表示接口的全限定名。

字段表集合

--20170701 00:10:00 编辑到字段表集合

引用

本文是对class文件的学习笔记,笔记的内容并非是原创,而是大量参考其它资料。在写作本文的过程中引用了以下资料,为为在此深深谢过以下资料的作者。

《The Java Virtual Machine Specification》

《深入理解Java虚拟机:JVM高级特性与最佳实践/周志明著.——2版.——北京:机械工业出版社,2013.6》

关于

本项目和文档中所用的内容仅供学习和研究之用,转载或引用时请指明出处。如果你对文档有疑问或问题,请在项目中给我留言或发email到 weiwei02@vip.qq.com

from weiwei.wang 20170625
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息