任何一个Class文件都对应着唯一一个类或接口的定义信息。
Class文件是一组以8位字节为基础单位的二进制流,当遇到需要占用8位字节以上空间的数据项时,会按照高位在前的方式分割成若干个8为字节进行存储。
存储格式:
无符号数和表
无符号数属于基本的数据类型,
u1、u2、u4、u8
分别表示1、2、4、8个字节的无符号数,可以用来描述数字、索引引用、数量值、或者按照UTF-8编码构成字符串值。
表是多个无符号数或者其他表作为数据项构成的复合数据类型,习惯性的以“_info”结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一个表。
Class文件格式
名称 | 描述 | 类型 | 数量 | |
---|---|---|---|---|
magic |
魔数 | u4 | 1 | |
minor_version |
次版本号 | u2 | 1 | |
major_version |
主版本号 | u2 | 1 | |
constant_pool_count |
常量池容量计数值 | u2 | 1 | |
constant_pool |
常量池 | cp_info | constant_pool_count-1 | |
access_flags |
访问标志 | u2 | 1 | |
this_class |
类索引 | u2 | 1 | |
` | super_class` | 父类索引 | u2 | 1 |
interfaces_count |
接口数量 | u2 | 1 | |
interfaces |
接口 | u2 | interfaces_count | |
fields_count |
属性数量 | u2 | 1 | |
fields |
属性 | field_info | fields_count | |
methods_count |
方法数量 | u2 | 1 | |
methods |
方法 | method_info | methods_count | |
attributes_count |
附加属性数量 | u2 | 1 | |
attributes |
附加属性 | attribute_info | attributes_count |
示例代码:
package com.jvm.test.demo.clazz;
public class TestClass {
private int m;
private int inc() {
return m + 1;
}
}
javac编译后的class文件
使用javap -verbose查看class文件
魔数
Class文件头四个字节。
字节码 | 描述 |
---|---|
cafe babe | 确定这个class文件是否为一个能被虚拟机接受的class是文件 |
Class文件版本
版本:第5和第6字节0000表示次版本号,第7和第8字节0034表示主版本号。对应jdk的版本。
字节码 | 描述 |
---|---|
0000 | 此版本号0 |
0034 | 主版本号52 |
高版本的JDK能向下兼容以前版本的class文件,但不能运行以后版本的class文件,即使文件格式并未发生任何变化,虚拟机也必须拒绝执行超过其版本号的class文件。
常量池
字面量:如文本字符串、声明为final的常量值等。
符号引用:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符
常量池中数量不固定,需有constant_pool_count代表数量。从1开始,因为为了满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况下把索引值置为0标识。
0x0013-->十进制为19,表示有18个常量。
常量池中每一项都是一个表。表开始第一位是一个u1类型的标志位(tag)代表属于哪个类型。每个类型有各自的结构。
类型 | 标志 | 描述 |
---|---|---|
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 | 字段或方法的部分符号引用 |
CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
CONSTANT_MethodType_info | 16 | 标识方法类型 |
CONSTANT_Dynamic_info | 17 | 表示一个动态计算常量 |
CONSTANT_InvokeDynamic_info | 18 | 表示一个动态方法调用点 |
CONSTANT_Module_info | 19 | 表示一个模块 |
CONSTANT_Package_info | 20 | 表示一个模块中开放或者导出的包 |
字节码 | 类型 | 描述 |
---|---|---|
0a 0004 000f | CONSTANT_Methodref_info | Object中init方法相关 |
09 0003 0010 | CONSTANT_Fieldref_info | 字段m相关 |
07 0011 | CONSTANT_Class_info | 类相关 |
07 0012 | CONSTANT_Class_info | 父类相关 |
01 0001 6d | CONSTANT_Utf8_info | 6d对应ascII中的m |
01 0001 49 | CONSTANT_Utf8_info | 49对应ascII中的I |
01 0006 3c696e69743e | CONSTANT_Utf8_info | 对应的ascII数据为 |
01 0003 282956 | CONSTANT_Utf8_info | 对应的ascII数据为()V |
01 0004 436f6465 | CONSTANT_Utf8_info | 对应的ascII数据为Code |
01 000f 4c696e654e756d6265725461626c65 | CONSTANT_Utf8_info | 对应的ascII数据为LineNumberTable |
01 0003 696e63 | CONSTANT_Utf8_info | 对应的ascII数据为inc |
01 0003 28 2949 | CONSTANT_Utf8_info | 对应的ascII数据为()I |
01 000a 536f7572636546696c65 | CONSTANT_Utf8_info | 对应的ascII数据为SourceFile |
01 000e 54657374436c6173732e6a617661 | CONSTANT_Utf8_info | 对应的ascII数据为TestClass.java |
0c 0007 0008 | CONSTANT_NameAndType_info | 方法和字段的相关 |
0c 0005 0006 | CONSTANT_NameAndType_info | 方法和字段的相关 |
01 0021 636f6d2f6a766d2f746573742f64656d6f2f636c617a7a2f54657374436c617373 | CONSTANT_Utf8_info | com/jvm/test/demo/clazz/TestClass |
01 0010 6a6176612f6c616e672f4f626a656374 | CONSTANT_Utf8_info | java/lang/Object |
Class文件中方法、字段等都需要引用CONSTANT_Utf8_info型常量来描述名称,所以CONSTANT_Utf8_info型常量最大长度就是Java中方法、字段名的最大长度,u2能表达的最大值65535。
访问标志
两个字节,用于识别一些类或者接口层次的访问信息。包括这个Class是类还是接口,是否被定义为public类型,是否定义为abstract类型等。
字节码 | 描述 |
---|---|
0021 | 为public所以最后一位为真0001,其余为为假。且1.2之后第二位真 |
类索引、父类索引、接口索引集合
使用这三项数据确定类的继承关系。
类索引:用于确定这个类的全限定名
父类索引:用于确定这个类的父类的全限定名。除了Object外,其余父类索引都不为0
接口索引集合:描述类实现了哪些接口,从左到右排列。
字节码 | 描述 |
---|---|
0003 | 类索引,指向索引号为3的类描述常量,从而找到类的全限定名字符串 |
0004 | 父类引,指向索引号为4的类描述常量,从而找到类的全限定名字符串 |
0000 | 标识接口数量为0,没有实现接口,不再占任何字节 |
字段表集合
用于描述接口或者类中声明的变量。包括类变量以及实例变量,不包括在方法内部声明的局部变量。
字段表结构
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | access_flags | 1 | 字段修饰符 |
u2 | name_index | 1 | 名称索引 |
u2 | descriptor_index | 1 | 描述符索引 |
u2 | attributes_count | 1 | 属性数量 |
attribute_info | attributes | attributes_count | 属性表集合 |
简单名称:没有类型和参数修饰的方法或者字段名称
全限定名:类的全路径,例如com/jvm/test/demo/clazz/TestClass;
描述符:描述字段的数据类型、方法的参数列表(包括数量、类型、顺序)和返回值
字段访问标志
标志 | 值 | 描述 |
---|---|---|
ACC_PUBLIC | 0x0001 | public 访问修饰符 |
ACC_PRIVATE | 0x0002 | private 访问修饰符 |
ACC_PROTECTED | 0x0004 | protected 访问修饰符 |
ACC_STATIC | 0x0008 | static |
ACC_FINAL | 0x0010 | final |
ACC_VOLATILE | 0x0040 | valatile |
ACC_TRANSIENT | 0x0080 | transient |
ACC_SYNTHETIC | 0x1000 | synthetic |
ACC_ENUM | 0x4000 | enum |
字节码 | 描述 |
---|---|
0001 | 表示1个字段数据 |
0002 | 标识访问标志,为private |
0005 | 字段的简单名称引用,到索引5 |
0006 | 引用到索引6,描述符I标识返回值为int类型 |
0000 | 标识没有额外信息存储 |
数组类型,每一位度前置一个”[“字符描述。例如java.lang.String[][]--->[[Ljava/lang/Strang
方法表集合
描述方法信息
方法表结构
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | access_flags | 1 | 名称修饰符 |
u2 | name_index | 1 | 名称索引 |
u2 | descriptor_index | 1 | 描述符索引 |
u2 | attributes_count | 1 | 属性数量 |
attribute_info | attributes | attributes_count | 属性表集合 |
方法访问标志:
方法里的Java代码,经过编译器编译成字节码指令后,存放在方法属性表集合”Code“中。
字节码 | 描述 |
---|---|
0002 | 表示2个方法 |
0001 | 标识访问标志,为public |
0007 | 索引7,方法名为 |
0008 | 索引8,返回值为()V |
0001 | 表示属性表集合有一属性 |
0009 | 属性名称索引为9,对应Code属性 |
属性表集合
Class文件、字段表、方法表都可以携带自己的属性表,用于描述场景专有信息。
属性表结构
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u1 | info | attribute_length |
Code属性
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | attribute_name_index | 1 | attribute_name_index是一项指向CONSTANT_Utf8_info型常量的索引,常量值固定为Code,它代表了该属性的属性名称 |
u4 | attribute_length | 1 | attribute_length指示了属性值的长度 |
u2 | max_stack | 1 | max_stack代表了操作数栈(Operand Stacks)深度的最大值。虚拟机运行的时候需要根据这个值来分配栈帧(StackFrame)中的操作栈深度。 |
u2 | max_locals | 1 | max_locals代表了局部变量表所需的存储空间,单位为Slot(虚拟机为局部变量分配内存所使用的最小单位) |
u4 | code_length | 1 | code_length代表字节码长度;理论上一个方法的字节码不超过u4,但实际是u2,如果超过这个限制,javac会拒绝 |
u1 | code | code_length | code是用于存储字节码指令的一系列字节流 |
u2 | exception_table_length | 1 | 异常表长度 |
exception_info | exception_table | exception_table_length | |
u2 | attributes_count | 1 | 属性数量 |
attribute_info | attributes | attributes_count |
字节码 | 描述 |
---|---|
0001 001d | 属性值长度为29 |
0001 | 最大栈深度为1 |
0001 | 最大局部变量空间为1 |
0000 00005 | 标识5个字节的指令 |
2a b7 00 01 b1 | 指令数据,方法执行指令操作步骤 |
0000 | 异常表长度为0,无异常信息 |
0001 | 标识有额外属性1个 |
000a | 额外属性索引为10,标识LineNumberTable属性 |
局部变量locals为1,因为任何实例方法里面,都可以通过”this“关键字访问到此方法所属的对象。Javac编译器编译时把对this关键字的访问转变为对一个普通方法参数的访问,在虚拟机调用实例方法时自动传入此参数。实例方法的局部变量表中至少存在一个执行当前对象实例的局部变量,局部变量表也会预留第一个Slot来存放对象实例的引用,方法参数从1开始计算。如果声明为static,那就是0。
Exceptions属性
作用是例举出方法中可能抛出的受检查异常,也就是方法描述时在throws后的异常。
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | attribute_name_index | 1 | 名称索引 |
u4 | attribute_length | 1 | 长度 |
u2 | number_of_exceptions | 1 | 异常种类数量 |
u2 | exception_index_table | number_of_exceptions | 执行常量池CONSTANT_Class_info的索引,代表受查异常类型 |
LineNumberTable属性
描述Java源码行号与字节码行号(字节码的偏移量)之间的对应关系。
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | attribute_name_index | 1 | 名称索引 |
u4 | attribute_length | 1 | 长度 |
u2 | line_number_table | 1 | 数量 |
line_number_info | line_number_table | line_number_table | 包括了start_pc标识字节码行号,line_number标识Java源码行号。都是u2类型数据项 |
字节码 | 描述 |
---|---|
0000 0006 | 属性值长度为6 |
0001 | 有1个行号对比 |
0000 0003 | 表示源码和字节码行号比对 |
0002 | 表示私有方法,inc |
000b | 名称索引,方法名称为inc |
000c | 描述符索引 |
0001 | 表示有1个属性集合 |
0009 | 属性索引,属性名称为Code |
0000 001f | 属性长度 |
0002 | 最大栈为2 |
0001 | 局部变量为1 |
0000 0007 | 字节码长度为7个 |
2ab4 0002 0460 ac | 字节码指令 |
0000 | 无异常 |
0001 | 有一个额外属性 |
000a | 名称索引,属性名称LineNumberTable |
0000 0006 | 属性长度为6 |
0001 | 数量为1 |
0000 0008 | 行号对比 |
LocalVariableTable属性
描述栈帧中局部变量表中的表量与Java源码中定义的变量之间的关系。
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | attribute_name_index | 1 | 名称索引 |
u4 | attribute_length | 1 | 长度 |
u2 | local_variable_table_length | 1 | 数量 |
local_variable_info | local_variable_table | local_variable_table_length |
local_variable_info
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | start_pc | 1 | 局部变量生命周期开始的字节码偏移量 |
u2 | length | 1 | 局部变量作用范围的长度 |
u2 | name_index | 1 | 指向常量池utf8常量的索引 |
u2 | descriptor_index | 1 | 局部变量的描述符 |
u2 | index | 1 | 局部编码在栈帧局部变量表中Slot的位置,数据类型为64位时,占用index以及index+1 |
SourceFile属性
用于记录生成这个Class文件的源码文件名称
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | attribute_name_index | 1 | 名称索引 |
u4 | attribute_length | 1 | 长度 |
u2 | sorucefile_index | 1 | 指向utf8常量的索引,值为源文件的名称 |
字节码 | 描述 |
---|---|
0001 | 表示有1个属性 |
000d | 索引13,属性名称SourceFile |
0000 0002 | 两个字节长度 |
000e | 名称索引TestClass.java |
ConstantValue属性
通知虚拟机自动为静态变量赋值,只有被static关键字修饰的变量(类变量)才可以使用这项属性。
对于非static类型的变量(也就是实例变量)的赋值是在实力构造器方法进行的;对于类变量,可以选择在类构造器 方法或使用ConstantValue属性。如果同时使用final和static修饰,且变量为基本数据类型或者String,就生成ConstantValue来进行初始化。否则,在 中进行初始化。
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | attribute_name_index | 1 | 名称索引 |
u4 | attribute_length | 1 | 固定为2 |
u2 | constantvalue_index | 1 | 标识常量池中一个字面量常量的引用 |
innerClasses属性
用于记录内部类与宿主类之间的关联。
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | attribute_name_index | 1 | 名称索引 |
u4 | attribute_length | 1 | 固定为2 |
u2 | number_of_classes | 1 | 内部类数量 |
inner_classes_info | inner_classes | number_of_classes | 内部类信息 |
inner_classes_info
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | inner_class_info_index | 1 | 内部类的符号引用 |
u2 | outer_class_info_index | 1 | 宿主类的符号引用 |
u2 | inner_name_index | 1 | 内部类的名称索引,如果是匿名内部类,值为0 |
u2 | inner_class_access_flags | 1 | 内部类的访问标志 |
inner_class_access_flags
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 内部类是否为public |
ACC_PRIVATE | 0x0002 | 内部类是否为private |
ACC_PROTECTED | 0x0004 | 内部类是否为protected |
ACC_STATIC | 0x0008 | 内部类是否为static |
ACC_FINAL | 0x0010 | 内部类是否为final |
ACC_INTERFACE | 0x0020 | 内部类是否为接口 |
ACC_ABSTRACT | 0x0400 | 内部类是否为abstract |
ACC_SYNTHETIC | 0x1000 | 内部类是否并发由用户代码产生 |
ACC_ANNOTATION | 0x2000 | 内部类是否是一个注解 |
ACC_ENUM | 0x4000 | 内部类是否是一个枚举 |
Deprecated及Synthetic属性
都是布尔属性。
Deprecated表示有@deprecated注解进行设置的代码
Synthetic属性和ACC_SYNTHETIC,非用户代码产生的类、方法、字段至少设置一个。实例构造器和类构造器 方法除外。
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | attribute_name_index | 1 | 名称索引 |
u4 | attribute_length | 1 | 长度 |
StackMapTable属性
位于Code属性的属性表中。会在虚拟机类加载的字节码验证阶段被新类型检查验证器使用,目的在于代替以前比较消耗性能的基于数据流分析的类型推导验证器。
包含0至多个栈映射帧,每个栈帧映射都代表了一个字节码偏移量,用于表示该执行到该字节码时局部变量表和操作数栈的验证类型。
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | attribute_name_index | 1 | 名称索引 |
u4 | attribute_length | 1 | 长度 |
u2 | number_of_entries | 1 | 栈帧数量 |
stack_map_frame | stack_map_frame_entries | number_of_entries | 栈帧信息 |
一个方法的Code属性之多只能有一个StackMapTable属性,否则会抛出ClassFormatError异常。
Signature属性
可出现在类、字段表和方发表结构的属性表中。任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量或参数化类型,Signature属性会记录泛型签名信息。JavaAPI能够获取泛型类型,数据来源也是这个属性。
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | attribute_name_index | 1 | 名称索引 |
u4 | attribute_length | 1 | 长度 |
u2 | signature | 1 | 属性信息 |
BootstrapMethods属性
位于类文件的属性表中。用于保存invokedynamic指令引用的引导方法限定符。类文件的属性表中最多有一个BootstrapMethods属性
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | attribute_name_index | 1 | 名称索引 |
u4 | attribute_length | 1 | 长度 |
u2 | num_bootstrap_methods | 1 | 数量 |
bootstrap_method | bootstrap_methods | num_bootstrap_methods | 信息 |
bootstrap_method
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | bootstrap_method_ref | 1 | 常量池的有效索引 |
u2 | num_bootstrap_arguments | 1 | 数量 |
u2 | bootstrap_arguments | num_bootstrap_arguments | 信息 |
MethodParameters属性
JDK8以后加入Class文件格式中。用来记录方法的各个形参名称和信息。
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | attribute_name_index | 1 | 名称索引 |
u4 | attribute_length | 1 | 长度 |
u1 | parameters_count | 1 | 数量 |
parameter | parameters | parameters_count | 信息 |
parameter
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | name_index | 1 | 参数的名称 |
u2 | access_flags | 1 | 参数的状态指示器 |
access_flags:以下一种或多种
0x0010(ACC_Final):表示该参数被final修饰
0x1000(ACC_SYNTHETIC):表示该参数并未出现在源文件,是编译器自动生成
0x8000(ACC_MANDATED):表示该参数是源文件中隐式定义的。