深机笔记 - 09 Class类文件的结构

《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》6.3节
本文关于Class文件结构以《Java虚拟机规范(第2版)》(1999年发布,对应JDK1.4时代Java虚拟机)中的定义为主线
一个Class文件对应唯一一个类或接口定义信息
但类或接口并不一定都定义在文件里(譬如类或接口也可以通过类加载器直接生成)
本文中的“Class文件”并不一定都以磁盘文件的形式存在

Class文件:

是以8位字节为基础单位的二进制流
各数据项目严格按照顺序紧凑排列,中间无任何分隔符
8位字节以上数据按高位在前方式分割成若干8位字节存储
Java虚拟机规范规定Class文件格式采用类似C语言结构体的伪结构存储数据
伪结构中只有两种数据类型:无符号数、表

无符号数:

属于基本数据类型
u1、u2、u4、u8分别代表1个字节、2个字节、4个字节、8个字节
可用来描述数字、索引引用、数量值或按UTF-8编码构成字符串值

表:

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


类型集合:

无符号数和表需描述类型相同但数量不定的数据时,会使用前置容量计数器加若干连续数据项的形式,这一系列连续的、某一类型的数据被称为某一类型的集合

Class文件(表)中各数据项含义:


1. 魔数(Magic Number)

名称:magic
u2类型,前4个字节
唯一作用是确定该文件是否为一个能被虚拟机接受的Class文件
使用魔数而非扩展名进行识别主要基于安全方面考虑,因文件扩展名可随意改动
值为:0xCAFEBABE(咖啡宝贝)

2. 次版本号(Minor Version)

名称:minor_version
u2类型,第5、6个字节

3. 主版本号(Major Version)

名称:minor_version
u2类型,第7、8个字节
Java版本号从45开始
JDK1.1后每个JDK大版本发布主版本号向上加1(JDK1.0~1.1使用45.0~45.3版本号)
高版本JDK能向下兼容低版本Class文件,但不能运行更高版本Class文件,即使文件格式未发生任何变化
JDK1.7可生成主版本号最大为51.0
Class文件版本号:


4. 常量池容量计数值

名称:constant_pool_count
u2类型,第9、10个字节
常量池入口
计数从1开始(其他项计数都从0开始)
0表示不引用任何一个常量池项目

5. 常量池

名称:constant_pool
cp_info类型,长度不固定
最繁琐的数据项
可理解为Class文件中的资源仓库
Class文件结构中与其他项目关联最多的数据类型
占用Class文件空间最大的数据项目之一
Class文件字节码分析工具:JDK/bin/javap,-verbose参数可输出字节码内容

主要存放两大类常量:字面量(Literal)、符号引用(Symbolic References)
1) 字面量,接近于Java语言层面常量概念,如文本字符串、声明为final的常量值
2) 符号引用,属于编译原理方面概念,包括三类常量:
a. 类和接口的全限定名(Fully Qualified Name)
b. 字段的名称和描述符(Descriptor)
c. 方法的名称和描述符
Class文件不保存方法、字段的最终内存布局信息
虚拟机运行时从常量池获得符号引用,在类创建货运行时解析、翻译(动态连接)到具体内存地址(最终内存布局)
C、C++在编译期有“连接”,可知道方法的最终内存布局信息

常量池中每项常量都是一个表
JDK1.7前共有11种表,JDK1.7为更好支持动态语言调用,新增3种:CONSTANT_MethodHandle_info、CONSTANT_MethodType_info、CONSTANT_InvokeDynamic_info
14种表开头都是标志位(tag),代表当前常量属于那种常量类型
各常量类型有各自表结构(cp_info)

14种常量类型(tag):

CONSTANT_Class_info类型:
代表一个类或者接口的符号引用

tag:标志位,用于区分常量类型
name_index:索引值,指向类或者接口全限定名地址
CONSTANT_Utf8_info类型:
代表UTF-8编码的字符串

tag:标志位,用于区分常量类型
length:字符串长度(字节)
bytes:长度为length的连续UTF-8缩略编码字符串
UTF-8缩略编码与普通UTF-8编码的区别:
'\u0001'到'\u007f'间字符(相当于1~127的ASCII码)缩略编码使用一个字节表示
'\u0080'到'\u07ff'间字符缩略编码用两个字节表示
'\u0800'到'\uffff'间字符缩略编码按普通UTF-8编码规则使用三个字节表示
CONSTANT_Utf8_info类型常量最大长度就是Java中方法、字段名的最大长度,即length的最大值65535

14种表结构:



6. 访问标记

名称:access_flags
u2类型
用于识别类或者接口的访问信息
如:Class是类还是接口、是否定义为public类型、是否定义为abstract类型、若是类,是否被声明为final等
16个标志位可用,当前只定义8个,其他一律为0(Java虚拟机规范定义前5个,JDK1.5增加后3个)
8个标志位及含义:


7. 类索引

名称:this_class
u2类型
定类的全限定名
指向CONSTANT_Class_info类型常量,该常量的name_index指向CONSTANT_Utf8_info类型常量

8. 父类索引

名称:this_classsuper_class)
u2类型
确定父类的全限定名
指向CONSTANT_Class_info类型常量,该常量的name_index指向CONSTANT_Utf8_info类型常量
Java语言不允许多重继承,父类索引只有一个
java.lang.Object父类索引为0,其他类都有父类(父类索引不为0)

9. 接口计数器

名称:interfaces_count
u2类型
表示接口索引集合容量
接口索引集合入口

10. 接口索引集合

名称:interfaces
u2类型,interfaces_count个u2类型数据集合
描述类实现了哪些接口
被实现的接口将按implements或extends顺序从左到右排列在接口索引集合
Class文件用类索引、父类索引、接口索引集合确定类继承关系

11. 字段计数器

名称:fields_count
u2类型
表示字段表集合容量
字段表集合入口

12.字段表集合

名称:fields
fields_info类型
描述接口或者类中声明的变量
包括类级变量、实例级变量
不包括方法内部声明的局部变量
不包含从父类、超类继承的字段
可能出现Java代码中不存在的字段,例,内部类自动添加指向外部类实例的字段
若描述符不一致,允许简单名称重名

字段表结构(fields_info):

1) access_flags:字段修饰符,与类中access_flags类似
字段访问标志位及含义:

由Java语言规则可知:
ACC_PUBLIC、ACC_PRIVATE、ACC_PROTECTED最多只可选其一
ACC_FINAL、ACC_VOLATILE不能同时选择
ACC_PUBLIC、ACC_STATIC、ACC_FINAL必须选择
2) name_index:字段简单名称,是对常量池的引用(文末有全限定名、简单名称、描述符的解释)
3) descriptor_index:字段和方法的描述符,是对常量池的引用
4) attributes_count:属性表计数器
5) attributes:attributes_info类型,属性表集合(具体内容见后文)

13. 方法计数器

名称:methods_count
u2类型

14. 方法表集合

名称:methods
method_info类型
与字段描述几乎完全一致
不包含未被重写的父类方法
可能出现编译器自动添加的方法,例,类构造器"<clinit>"方法、实例构造器"<init>"方法
方法中的代码被编译成字节码指令存放attributes_info的Code属性中(具体内容见后文)
描述符不完全一致的方法可共存

方法表结构(method_info):

1) access_flags:访问标志

2) name_index:名称索引
3) descriptor_index:描述符索引
4) attributes_count:属性表计数器
5) attributes:attributes_info类型,属性表集合(具体内容见后文)

15. 属性表(attribute_info)计数器

名称:attributes_count
u2类型

16. 属性表(attribute_info)集合

名称:attributes
attribute_info类型
描述某些场景专有的信息
属性名称(attribute_name_index)从常量池引用CONSTANT_Utf8_info类型常量来表示
属性值(info)结构自定义,需u4类型长度属性(attribute_length)说明属性值所占用位数

属性表结构(attribute_info):

任何人可向属性表中写入自定义的、不重名的属性信息,虚拟机运行时会忽略不认识的属性
不要求各属性表有严格顺序
Class文件、字段表、方法表可携带自己的属性表集合
《Java虚拟机规范(第2版)》预定义9项属性,《Java虚拟机规范(JavaSE7)》增加到21项:



关键常用属性:
1) Code属性
存储方法体代码编译成的字节码
不是必须存在,例,接口、抽象方法不存在Code属性
表结构:

a. attribute_name_index,该属性的属性名称,指向CONSTANT_Utf8_info类型的索引,常量值,固定为"Code"
b. attribute_length:属性值长度
c. max_stack:操作数栈(Operand Stacks)深度最大值,方法执行时操作数栈不会超过这个深度,虚拟机根据该值分配栈帧(Stack Frame)中操作栈深度
d. max_locals:局部变量表所需存储空间,单位Slot
Slot:
虚拟机为局部变量分配内存所使用的最小单位
byte、char、float、int、short、boolean和returnAddress等长度不超过32位的数据类型占用1个Slot
double和long两种64位数据类型占用2个Slot
局部变量所占Slot之和不等于max_locals值,因Slot可重用,代码执行超出一个局部变量作用域时,该局部变量所占Slot可被其他局部变量使用
方法参数(包括实例方法中隐藏参数"this")、显式异常处理器参数(Exception Handle rParameter,即try-catch中catch块所定义异常)、方法体中定义的局部变量都存放在局部变量表
e. code_length:字节码长度,u4类型,但虚拟机规范限制一个方法不允许超过65535条字节码指令,即实际只使用了u2长度,若超过该限制Javac编译器会拒绝编译
f. code:用于存储字节码指令的一系列字节流。每个指令是一个u1类型单字节,共可表达256条指令,目前Java虚拟机规范已定义约200条编码值对应的指令含义,具体对应关系需查表
g. exception_table_length:显式异常处理表计数器
h. exception_table:显式异常处理表集合,exception_info类型
显式异常处理表结构(exception_info):

含义:
若字节码在第start_pc至end_pc(不含)行间出现catch_type(指向一个CONSTANT_Class_info类型常量的索引)或其子类异常,则转到第handler_pc行处理
若catch_type值为0,则任意异常都转到handler_pc行处理
JDK1.7起,禁止Class文件出现jsr、ret指令实现finally语句

2) Exceptions属性
与Code属性平级
列举出方法中可能抛出的受查异常(Checked Excepitons),即方法描述中throws关键字后面列举的异常
表结构:

a. attribute_name_index,该属性的属性名称,指向CONSTANT_Utf8_info类型的索引,常量值,固定为"Exceptions"
b. attribute_length:属性值长度
c. number_of_exceptions:方法可能抛出受查异常种类的数量
e. exception_index_table:受查异常,指向常量池中CONSTANT_Class_info类型型常量的索引,代表该受查异常类型

3) LineNumberTable属性
描述Java源码行号与字节码行号(字节码偏移量)间对应关系
非运行时必需的属性
默认会生成到Class文件中
可在Javac中使用-g:none、-g:lines选项取消、生成这项信息
若不生成LineNumberTable属性,则程序抛出异常时堆栈中不显示出错行号,且调试程序时无法按照源码行设置断点
表结构:

a. attribute_name_index,该属性的属性名称,指向CONSTANT_Utf8_info类型的索引,常量值,固定为"LineNumberTable"
b. attribute_length:属性值长度
c. line_number_table_length:行号表集合计数器,即行数
d. line_number_table:行号表集合,行号表(line_number_info),包括start_pc(字节码行号)、line_number(Java源码行号)两个u2类型数据项

4) LocalVariableTable属性
描述栈帧中局部变量表中变量与Java源码中变量的关系

5) SourceFile属性
记录源码件名称

6) ConstantValue属性
通知虚拟机自动为静态变量赋值
只有static关键字修饰的变量(类变量)才可使用该属性

7) InnerClasses属性
记录内部类与宿主类间关联关系

8) Deprecated属性
布尔属性
表示某个类、字段或者方法,已经被程序作者定为不再推荐使用
可通过在代码中使用@deprecated注释进行设置

9) Synthetic属性
布尔属性
表示此字段或者方法不是由Java源码直接产生而是由编译器自行添加的

10) StackMapTable属性
JDK1.6增加
是一个复杂的变长属性
虚拟机类加载的字节码验证阶段,新类型检查验证器(Type Checker)使用
目的在于代替比较消耗性能的基于数据流分析的类型推导验证器

11) Signature属性
JDK1.5增加
是一个可选的定长属性
可出现在类、属性表和方法表结构的属性表中
任何类、接口、初始化方法、成员的泛型签名若包含了类型变量(Type Variables)、参数化类型(ParameterizedTypes),则Signature属性会为其记录泛型签名信息

12) BootstrapMethods属性
JDK1.7增加
是一个复杂的变长属性
位于类文件的属性表中
用于保存invokedynamic指令引用的引导方法限定符

全限定名、简单名称、描述符的解释:

全限定名:类全名中的“.”替换成“/”,在结尾加“;”表示全限定名结束
简单名称:没有类型、参数修饰的方法或字段名称,例,inc()方法、m字段的简单名称是"inc"、"m"
方法和字段的描述符:用来描述字段的数据类型、方法的参数列表(包括数量、类型及顺序)和返回值
描述符规则:
基本数据类型(byte、char、double、float、int、long、short、boolean)及void类型用一个大写字符表示
对象类型用字符L加对象的全限定名表示
数组类型,每一个维度使用一个前置“[”字符描述,例,"java.lang.String[][]"记录为"[[Ljava/lang/String;"
描述符描述方法,按照先参数列表后返回值顺序,参数列表按参数顺序放在小括号“()”内
例,int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)的描述符为"([CII[CIII)I"

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值