Java虚拟机规范规定Class文件格式统一采用一种类似于C语言结构体的伪结构体来存储数据,
这种伪结构体存储两种数据类型:无符号数和表。

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

表:由多个无符号数或者其他表作为数据项构成的符合数据类型,所以表都习惯性地以_info结尾。
表用于描述有层次关系的符合结构的数据,整个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_flag 1
u2 this_class 1
u2 super_class 1
u2 interface_count 1
u2 interfaces interface_count
u2 fields_count 1
field_info fields fields_count
u2 method_count 1
method_info methods methods_count
u2 attributes_count 1
attribute_info attributes attributes_count

魔数

每个Class文件的头四个字节称为魔数,其值的16进制表示为0xCAFEBABE(换算为二进制为1100 1010 1111 1110 1011 1010 1011 1110),虚拟机在加载类时分析该文件是否为Class文件。

版本号

第5、6个字节表示次版本号。第7、8个字节表示主版本号。
高版本的JDK能够向下兼容低版本的Class文件,虚拟机会拒绝执行超过其版本的Class文件。

常量池

常量池中存放两大类常量:

  • 字面量常量:
    比较接近Java语言层面的常量概念,比如字符串和被声明为final的常量值。对于范围在-127~128之间的包装类型也是常量。
  • 符号引用常量:属于编译原理方面的概念,包含
    1. 类和接口的全限定名
    2. 字段的名称和描述符
    3. 方法的名称和描述符

常量池的大小不固定,用两个字节表示,第零项空出来,为了满足某些指向常量池的索引值的数据“不引用任何一个常量池项目”。

常量池的每一项常量都是一个表,这个表的第一位都是一个u1类型的标识位。

下面是常量池中的14种项目类型:
常量池中的14种项目类型

下面是常量池中的14种项目类型的结构表:

常量池中的表类型

常量池中的表类型

常量池中数据项类型 类型标志 类型描述
CONSTANT_Utf8 1 UTF-8编码的Unicode字符串
CONSTANT_Integer 3 int类型字面值
CONSTANT_Float 4 float类型字面值
CONSTANT_Long 5 long类型字面值
CONSTANT_Double 6 double类型字面值
CONSTANT_Class 7 对一个类或接口的符号引用
CONSTANT_String 8 String类型字面值
CONSTANT_Fieldref 9 对一个字段的符号引用
CONSTANT_Methodref 10 对一个类中声明的方法的符号引用
CONSTANT_InterfaceMethodref 11 对一个接口中声明的方法的符号引用
CONSTANT_NameAndType 12 对一个字段或方法的部分符号引用

访问标志

访问标志标示了当前类的修饰符,比如 public / final / super / interface / enum / abstract / annotation / synthetic

标志名称 标志值 含义
ACC_PUBLIC 0x00 01 是否为Public类型
ACC_FINAL 0x00 10 是否被声明为final,只有类可以设置
ACC_SUPER 0x00 20 是否允许使用invokespecial字节码指令的新语义.
ACC_INTERFACE 0x02 00 标志这是一个接口
ACC_ABSTRACT 0x04 00 是否为abstract类型,对于接口或者抽象类来说,次标志值为真,其他类型为假
ACC_SYNTHETIC 0x10 00 标志这个类并非由用户代码产生
ACC_ANNOTATION 0x20 00 标志这是一个注解
ACC_ENUM 0x40 00 标志这是一个枚举

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

  • 类索引:this_class, 用于确定这个类的全限定名
  • 父类索引:super_class, 父类索引用于确定这个类的父类的全限定名
  • 接口索引集合:interfaces, 用于描述这个类实现了哪些接口

字段表集合

字段表集合(field_info)用于描述接口或者类中声明的变量,包含类变量和实例变量,但不包含方法内部声明的局部变量。

|类型|名称|数量|
|—-|—-|—-|—-|
|u2 |access_flag|1|访问修饰符|
|u2 |name_index | 1| 字段的简单名称,如String str = “123”, str就是简单名称|
|u2 |descritpor_index|1| 字段或者方法的描述符|
|u2 |attributes_count |1 | |
|attribute_info | attributes | attributes_count| |

访问修饰符

标志名称 标志值 含义
ACC_PUBLIC 0x0001 字段是否为public
ACC_PRIVATE 0x0002 字段是否为private
ACC_PROTECTED 0x0004 字段是否为protected
ACC_STATIC 0x0008 字段是否为static
ACC_FINAL 0x0010 字段是否为final
ACC_VOLATILE 0x0040 字段是否为volatile
ACC_TRANSTENT 0x0080 字段是否为transient
ACC_SYNCHETIC 0x1000 字段是否为由编译器自动产生
ACC_ENUM 0x4000 字段是否为enum

描述符:
描述符的作用是用来描述字段的数据类型、方法的参数列表(数量、类型、顺序)和返回值。
根据描述符的规则:基本数据类型以及代表无返回值的void类型都用一个大写的字符来表示;
而对象类型则用字符L加对象的全限定名来描述;

标志符号 含义
B byte
C char
D double
F float
I int
J long
S short
Z boolean
V void 按照先参数列表后返回值的顺序描述,参数列表按照顺序放在”()”内部,String toString()描述为V()java.lang.String
L 对象类型
[ 数组类型 [I: 代表int[], [[I:代表int[][]

方法表集合

Class 文件中对方法的描述和对字段的描述完全是一致的, 方法表中的结构和字段表中的接口一样。

因为volatile关键字和transient关键字不能修饰方法,所以方法表的访问标志中没有ACC_VOLATILE和ACC_TRANSIENT。 但是增加了synchronized,native,abstract,strictfp(strict float point, 精确的浮点数)关键字的修饰符。
对于方法里的代码,经过编译器变异常字节码指令后,存放在方法属性表中一个名为code的属性里。

标志名称 标志值 含义
ACC_PUBLIC 0x00 01 方法是否为public
ACC_PRIVATE 0x00 02 方法是否为private
ACC_PROTECTED 0x00 04 方法是否为protected
ACC_STATIC 0x00 08 方法是否为static
ACC_FINAL 0x00 10 方法是否为final
ACC_SYHCHRONRIZED 0x00 20 方法是否为synchronized
ACC_BRIDGE 0x00 40 方法是否是有编译器产生的方法
ACC_VARARGS 0x00 80 方法是否接受参数
ACC_NATIVE 0x01 00 方法是否为native
ACC_ABSTRACT 0x04 00 方法是否为abstract
ACC_STRICTFP 0x08 00 方法是否为strictfp (strict-float-point,精准的浮点数)
ACC_SYNTHETIC 0x10 00 方法是否是有编译器自动产生的

属性表集合

在Class文件、字段表、方法表中都可以携带自己的属性表集合(attribute_info),用于描述某些场景的专有信息。

不强制要求各属性表的顺序,只要不与已有属性表重名即可。
任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机在运行时会忽略不认识的属性。

属性名称 使用位置 含义
Code 方法表 Java代码编译成的字节码指令
ConstantValue 字段表 final关键字定义的常量池
Deprecated 类,方法,字段表 被声明为deprecated的方法和字段
Exceptions 方法表 方法抛出的异常
EnclosingMethod 类文件 仅当一个类为局部类或者匿名类是才能拥有这个属性,这个属性用于标识这个类所在的外围方法
InnerClass 类文件 内部类列表
LineNumberTable Code属性 Java源码的行号与字节码指令的对应关系
LocalVariableTable Code属性 方法的局部变量描述
StackMapTable Code属性 JDK1.6中新增的属性,供新的类型检查检验器检查和处理目标方法的局部变量和操作数有所需要的类是否匹配
Signature 类,方法表,字段表 用于支持泛型情况下的方法签名
SourceFile 类文件 记录源文件名称
SourceDebugExtension 类文件 用于存储额外的调试信息
Synthetic 类,方法表,字段表 标志方法或字段为编译器自动生成的
LocalVariableTypeTable 使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
RuntimeVisibleAnnotations 类,方法表,字段表 为动态注解提供支持
RuntimeInvisibleAnnotations 表,方法表,字段表 用于指明哪些注解是运行时不可见的
RuntimeVisibleParameterAnnotation 方法表 作用与RuntimeVisibleAnnotations属性类似,只不过作用对象为方法
RuntimeInvisibleParameterAnnotation 方法表 作用与RuntimeInvisibleAnnotations属性类似,作用对象哪个为方法参数
AnnotationDefault 方法表 用于记录注解类元素的默认值
BootstrapMethods 类文件 用于保存invokeddynamic指令引用的引导方式限定符

对于看了文章还有点懵的同学,看这里的视频讲解,忽略广告。《全网最牛JVM字节码结构分析、Class类文件核心结构》。