「JVM 执行子系统」Class 文件结构解析

本文详细介绍了JavaClass文件的结构,包括魔数、版本号、常量池、访问标志、类索引、字段表集合、方法表集合和属性表集合。这些内容构成了JVM执行的基础,确保了平台无关性和语言无关性。Class文件的字节码格式保证了向后兼容性,并且通过常量池存储符号引用和字面量。文章还提到了方法的Code属性,包含字节码指令,以及各种预定义的属性,如ConstantValue和ExceptionTable。
摘要由CSDN通过智能技术生成

我们所编写的各类语言的程序编译成二进制机器码(Native Code)已不再是唯一的选择,越来越多程序语言编译选择与操作系统、机器指令集无关的、平台中立的输出格式;

无关性基石

各种不同平台的 JVM,以及所有平台统一支持的编译结果存储格式(字节码,Byte Code)是构成平台无关性语言无关性的基石;

  • 语言无关性,JVM 不与 Java 语言在内的任何程序语言绑定,只与Class 文件的二进制字节码格式绑定,其中 Class 文件包含 JVM 指令集、符号表、其他辅助信息等;

Java 技术非常良好的向后兼容性得益于 Class 文件结构的稳定性;Class 文件结构基本只在原有基础上新增内容、扩充功能,而非在已定义的内容上做修改;

任意一个 Class 文件都对应着唯一的一个类或接口的定义信息(package-info.class、module-info.class 除外),反之则不一定(类或接口的动态生成,无需落到磁盘);其不像 XML 等描述语言,没有任何分隔符,严格限定了数据项的顺序和数量、数据存储的字节序等;

Class 文件格式只存在两种数据类型:

  • 无符号数,以 u1、u2、u4、u8 分别代表 1、2、4、8 个字节的无符号数,可以用来描述数字、索引引用、数量值、UTF-8 编码代表的字符等;
  • ,由多个无符号数或其他表构成的复合数据类型,表的命名习惯以 _info 结尾;

Class 文件格式

类型名称数量
u4magic1
u2minor_version1
u2major_version1
u2constant_pool_count1
cp_infoconstant_poolconstant_pool_count-1
u2access_flags1
u2this_class1
u2super_class1
u2interfaces_count1
u2interfacesinterfaces_count
u2fields_count1
field_infofieldsfields_counts
u2methods_count1
method_infomethodsmethods_count
u2attributes_count1
attribute_infoattributesattributes_count

1. 魔数

Class 文件的头 4 个字节即为魔数(Magic Number),其唯一作用是确定 Class 文件格式是否能被 JVM 接受(类似扩展名);Class 文件的魔数为 OxCAFEBABE(咖啡宝贝);

2. Class 文件的版本

Class 文件中紧跟魔数的 4 个字节是 Class 文件的版本号,第 5、6 字节是 Minor Version(次版本号),第 7、8 字节是 Major Version(主版本号);
高版本 JDK 可以向下兼容旧版 Class 文件,但面对超过 JDK 版本的 Class 文件,即使文件格式未发生变化也是拒绝执行的;

Java 文件版本号

JDK 版本-target 参数-source 参数版本号
JDK 1.1.8不支持 target 参数不支持 source 参数45.3
JDK 1.2.2不带(默认为 -target 1.1)1.1 ~ 1.245.3
JDK 1.2.2-target 1.21.1 ~ 1.246.0
JDK 1.3.1_19不带(默认为 -target 1.1)1.1 ~ 1.345.3
JDK 1.3.1_19-target 1.31.1 ~ 1.347.0
JDK 1.4.2_10不带(默认为 -target 1.2)1.1 ~ 1.446.0
JDK 1.4.2_10-target 1.41.1 ~ 1.448.0
JDK 5.0_11不带(默认为 -target 1.5)1.1 ~ 1.549.0
JDK 5.0_11-target 1.4 -source 1.41.1 ~ 1.548.0
JDK 6不带(默认为 -target 6)1.1 ~ 650.0
JDK 7不带(默认为 -target 7)1.1 ~ 751.0
JDK 8不带(默认为 -target 8)1.1 ~ 852.0
JDK 9不带(默认为 -target 9)6 ~ 953.0
JDK 10不带(默认为 -target 10)6 ~ 1054.0
JDK 11不带(默认为 -target 11)6 ~ 1155.0
JDK 12不带(默认为 -target 12)6 ~ 1256.0
JDK 13不带(默认为 -target 13)6 ~ 1357.0

Minor Version 只在 JDK 2 短暂使用过,直到 JDK 12 中间版本的 JDK 的 Minor Version 皆固定为 0;
JDK 12 开始,带有公测特性的技术预览版本会将 Minor Version 置为 65535;

3. 常量池

Class 文件中紧接 Major Version 的是常量池;是 Class 文件结构中与其他项关联最多的数据,也是占用空间最大的数据项之一;

常量池中常量的数量不固定,因此常量池入口的一个 u2 型数据表示了常量池容量(constant_pool_count);该容量计数以 1 开始(不是 0,其他如接口索引集合、字段表集合、方发表集合等,是从 0 开始索引);

constant_pool_count 表示常量个数加 1,这个 1 表达不引用任何一个常量池项,索引位占用了 0

常量池类别

  • 字面量(Literal),类似常量的概念,如字符串、final 的常量值等;
  • 符号引用(Symbolic References),属于变异原理方面的概念;
    • 被模块导出或开放的包(Package);
    • 类或接口的全限定名(Fully Qualified Name);
    • 字段的名称和描述符(Descriptor);
    • 方法的名称和描述符;
    • 方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic);
    • 动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant);

常量池中的每一项都是一个表,表的结构由第一个 u1 类型的标志位区分;截止 JDK 13,常量表共有 17 种不同类型,每个类型各有完全独立的数据结构;

类型标志描述
CONSTANT_Utf8_info1UTF-8 编码的字符串
CONSTANT_Integer_info3整型字面量
CONSTANT_Flaot_info4浮点型字面量
CONSTANT_Long_info5长整型字面量
CONSTANT_Double_info6双精度浮点型字面量
CONSTANT_Class_info7类或接口的符号引用
CONSTANT_String_info8字符串类型字面量
CONSTANT_Fieldref_info9字段的符号引用
CONSTANT_Methodref_info10类中方法的符号引用
CONSTANT_InterfaceMethodref_info11接口中方法的符号引用
CONSTANT_NameAndType_info12字段或方法的部分符号引用
CONSTANT_MethodHandle_info15方法句柄
CONSTANT_MethodType_info16方法类型
CONSTANT_Dynamic_info17一个动态计算常量
CONSTANT_InvokeDynamic_info18一个动态方法调用点
CONSTANT_Module_info19一个模块
CONSTANT_Package_info20一个模块中开放或导出的包

通过 javap 解析如下代码编译后的 Class 文件;与 Class 文件的 Hex 格式对比(对照字节码数据类型翻译对比);

Java 源码

package edu.aurelius.jvm.clazz;

public class TestClass {
    private int m;

    public int inc() {
        return m + 1;
    }
}

Class 文件 Hex 格式

请添加图片描述

javap 解析结果

javap -verbose TestClass
Warning: Binary file TestClass contains edu.aurelius.jvm.clazz.TestClass
Classfile /jvm-best-practices/target/classes/edu/aurelius/jvm/clazz/TestClass.class
  Last modified Feb 5, 2023; size 399 bytes
  MD5 checksum 889e306f2098bb3e76eacac7fba77eef
  Compiled from "TestClass.java"
public class edu.aurelius.jvm.clazz.TestClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#18         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#19         // edu/aurelius/jvm/clazz/TestClass.m:I
   #3 = Class              #20            // edu/aurelius/jvm/clazz/TestClass
   #4 = Class              #21            // java/lang/Object
   #5 = Utf8               m
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Ledu/aurelius/jvm/clazz/TestClass;
  #14 = Utf8               inc
  #15 = Utf8               ()I
  #16 = Utf8               SourceFile
  #17 = Utf8               TestClass.java
  #18 = NameAndType        #7:#8          // "<init>":()V
  #19 = NameAndType        #5:#6          // m:I
  #20 = Utf8               edu/aurelius/jvm/clazz/TestClass
  #21 = Utf8               java/lang/Object
{
  public edu.aurelius.jvm.clazz.TestClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ledu/aurelius/jvm/clazz/TestClass;

  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Ledu/aurelius/jvm/clazz/TestClass;
}
SourceFile: "TestClass.java"

4. 访问标志

Class 文件中紧跟常量池的是访问标志(access_flags),用于识别一些类或接口的访问信息(是类还是接口、是否 public、是否 abstract、是否 final 等);

标志含义对照表

标志名称标志值含义
ACC_PUBLIC0x0001是否 public 类型
ACC_FINAL0x0010是否声明 final,仅限
ACC_SUPER0x0020是否允许使用 invokespecial 字节码指令的新语义,JDK 1.0.2 之后编译的类必须为真
ACC_INTERFACE0x0200是否接口
ACC_ABSTRACT0x0400是否 abstract 类型,对接口和抽象类是真,其他为假
ACC_SYNTHETIC0x1000标识这个类并非用户代码产生
ACC_ANNOTATION0x2000是否注解
ACC_ENUM0x4000是否枚举
ACC_MODULE0x8000是否模块

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

Class 文件紧接访问标志之后的是类索引(u2)、父类索引(u2)、接口索引集合(u2 集合);用于确定该类型的继承关系;

  • 类索引确定这个类的全限定名;
  • 父类索引确定这个类的父类的全限定名;除了 java.lang.Object,所有 Java 类都有父类;
  • 接口索引集合描述该类实现了哪些接口;接口索引集合入口的第一个 u2 类型数据表示接口计数器(interfaces_count),即索引表的容量;

6. 字段表集合

字段表(field_info)用于描述接口或类中声明的变量(不包括定义在方法中的局部变量);

字段表结构

类型名称描述数量
u2access_flags字段修饰符,与类的 access_flags 类似1
u2name_index字段的简单名称,如方法 inc()字段 m的简单名称分别是 incm1
u2descriptor_index字段和方法的描述符,描述字段的类型、方法的参数列表(包括数量、类型、顺序)和返回值1
u2attributes_count1
u2attributes用于存储一些额外信息,如 Constant Value 属性描述字段为常量attributes_count

字段访问标识

标志名称标志值含义
ACC_PUBLIC0x0001是否 public
ACC_PRIVATE0x0002是否 private
ACC_PROTECTED0x0004是否 protected
ACC_STATIC0x0008是否 static
ACC_FINAL0x0010是否 final
ACC_VOLATILE0x0040是否 volatile
ACC_TRANSIENT0x0080是否 transient
ACC_SYNTHETIC0x1000是否编译器自动生成
ACC_ENUM0x4000是否枚举

descriptor

标识字符含义
B基本类型 byte
C基本类型 char
D基本类型 double
F基本类型 float
I基本类型 int
J基本类型 long
S基本类型 short
Z基本类型 boolean
V特殊类型 void
L对象类型,如 Ljava/lang/Ojbect;

数组类型,每一维度将使用一个前置的 [ 字符来描述,如 java.lang.String[][] 类型的二维数组呗记录成 [[Ljava/lang/String;
字段表集合不会列出父类或父接口的字段,但可能出现原 Java 代码中不存在的字段,如内部类为了保持对外部类的访问下,会自动添加纸箱外部类实例的字段;

7. 方法表集合

Class 文件存储的对方法的描述与对字段的描述几乎完全一致;方法表的结构与字段表的结构一样,依次是访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes),详情参照上文字段表结构描述;

方法访问标识相比字段访问标志,少了 volatile、transient、enum,多了 synchronized、native、strictfp、abstract;

方法访问标志

标志名称标志值含义
ACC_PUBLIC0x0001是否 public
ACC_PRIVATE0x0002是否 private
ACC_PROTECTED0x0004是否 protected
ACC_STATIC0x0008是否 static
ACC_FINAL0x0010是否 final
ACC_SYNCHRONIZED0x0020是否 synchronized
ACC_BRIDGE0x0040是否由编译器产生的桥接方法
ACC_VARARGS0x0080是否接受不定参数
ACC_NATIVE0x0100是否 native
ACC_ABSTRACT0x0400是否 abstract
ACC_STRICT0x0800是否 strictfp
ACC_SYNTHETIC0x1000是否编译器自动生成

方法里面的代码经过 javac 编译后成字节码后,存放在方法属性集合中的名为 Code 的属性中;

在 Java 语言中重载(Overload)一个方法,需要与原方法具有相同的简单名称和特征签名(方法的各个参数在常量池中字段符号引用的集合),因此无法仅仅依靠返回值不同重载一个方法;但在 Class 文件格式中,只要方法的描述符不完全一致,是运行仅仅返回值不一的两个方法共存于同一 Class 文件的;

8. 属性表集合

字段表、方法表都可以携带自己的属性表集合,以描述某些场景的专有信息;

《Java 虚拟机规范》允许只要不与已有属性名重复,任何人实现的编译器都可以向属性表写入自己定义的属性信息;JVM 运行时会自动忽略不认识的属性;

属性表结构

类型名称数量
u2attribute_name_index1
u4attribute_length1
u1infoattribute_length

属性的名称是从常量池引用的一个 CONSTANT_Utf8_info 类型的常量表示的;
属性值的结构完全自定义;

虚拟机规范预定义的属性

属性名称使用位置含义
Code方法表Java 代码编译成的字节码指令
ConstantValue字段表由 final 关键字定义的常量值,会通知 JVM 自动赋值
Deprecated类、方发表、字段表被声明为 deprecated 的类、方法和字段
Exceptions方法表方法抛出的异常列表,方法描述时 throws 关键后列举的受查异常
EnclosingMethod类文件局部类或匿名类才拥有的属性,标示类所在的外围方法
InnerClasses类文件内部类列表
LineNumnberTableCode 属性Java 源码的行号与字节码指令的对应关系,与抛异常的行号提示、调试时的行断点设置有关
LocalVariableTableCode 属性方法的局部变量表描述,栈帧中变量与 Java 源码中变量的映射关系;与方法调用和调试时参数名称的显示有关
StackMapTableCode 属性JDK 6 新增,检查目标方法的局部变量和操作数栈所需类型是否匹配(Type Checker)
Signature类、方法表、字段表JDK 5 新增,用于支持泛型的方法前面,包含 Type Variables 或 Parameterized Types 的类、接口、初始化方法或成员的泛型前面会被 Signature 记录泛型信息,以应对 Java 的泛型擦拭法实现
SourceFile类文件记录源文件名称,与抛异常时显示错误代码所在文件名有关
SourceDebugExtension类文件JDK 5 新增,存储额外的调试信息
Synthetic类、方法表、字段表标识方法或字段为编译器自动生成的

Code 属性表结构

类型名称数量说明
u2attribute_name_index1CONSTANT_Utf8_info 型常量的索引,代表该属性的名称,固定为 Code
u4attribute_length1属性长度
u2max_stack1操作数栈深度的最大值
u2max_locals1局部变量表所需的存储空间,单位为变量槽(slot),32 位,除了 long 和 double 需要两个 slot,其他数据类型都只占用 1 slot,非 static 方法自带一个 this 变量,变量槽永远大于 0
u4code_length1字节码长度
u1codecode_length字节码指令的一些列字节流
u2exception_table_length1异常处理表长度
exception_infoexception_tableexception_table_length显式异常处理表集合
u2attributes_count1
attribute_infoattributesattributes_count

上一篇:「JVM 性能调优」应用程序启动耗时与延时优化
下一篇:「JVM 执行子系统」字节码指令简介

PS:感谢每一位志同道合者的阅读,欢迎关注、评论、赞!


参考资料:

  • [1]《深入理解 Java 虚拟机》
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三余知行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值