什么是字节码

字节码文件是 Java 虚拟机(JVM)能够识别和执行的中间代码文件,通常以 .class 后缀存储。它由 Java 或其他 JVM 语言(Kotlin、Scala 等)的编译器生成,是连接源代码与机器码的桥梁。

JVM 平台上的字节码文件

类型 扩展名 说明
标准 Java 字节码 .class 由 javac 编译 Java 源文件生成,严格遵循 JVM 规范。
其他 JVM 语言字节码 .class Kotlin、Groovy、Scala、Clojure 等语言编译后也生成标准的 .class 文件,只是内部常量池和方法体不同。
动态生成的字节码 无独立文件 运行时通过 ASM、CGLib、ByteBuddy 等库在内存中直接生成字节码(如 Spring AOP 代理类),不写入磁盘。
打包后的字节码 .jar、.war、.ear 本质上是 ZIP 压缩包,内含多个 .class 文件及其他资源。

网络传输或存储的序列化二进制流也是二进制数据,但它 不是字节码文件。两者虽然都是二进制流,但用途和格式完全不同:

字节码文件与序列化数据

对比维度 字节码文件 (.class) 序列化数据 (.ser / 网络流)
用途 JVM 加载执行类 持久化对象状态或网络传输
格式 严格遵循 JVM 规范(魔数 CAFEBABE、常量池、方法表等) 遵循 Java 序列化协议(魔数 ACED0005、类描述符、字段值等)
消费方 JVM 的类加载子系统 反序列化时的 ObjectInputStream
生命周期 编译时生成,运行时加载 运行时动态产生,用于跨进程通信或存储

总结:序列化二进制流是 对象数据的快照,字节码文件是 类定义的蓝图。两者都可以在网络传输,但前者用于传递数据,后者用于传递可执行代码。

字节码结构

image-20260710104502076

image-20260710124850840

字节码文件整体结构总览

序号 数据段名称 数据类型(大小) 说明
1 魔数 (Magic Number) u4 (4 字节) 固定值为 0xCAFEBABE,用于标识该文件是一个合法的 Java Class 文件。
2 次版本号 (Minor Version) u2 (2 字节) 编译此 Class 文件所使用的 JDK 副版本号。
3 主版本号 (Major Version) u2 (2 字节) 编译此 Class 文件所使用的 JDK 主版本号(例如 61 对应 Java 17)。
4 常量池计数 u2 (2 字节) 表示常量池中常量的数量(从 1 开始计数,0 保留)。
5 常量池 (Constant Pool) cp_info [] (变长) 存储字面量和符号引用(如类名、方法名、字段名、字符串等)。
6 访问标志 u2 (2 字节) 标识该类或接口的访问权限与属性(如 public、abstract、final 等)。
7 当前类索引 u2 (2 字节) 指向常量池中的一个 CONSTANT_Class_info,表示本类的全限定名。
8 父类索引 u2 (2 字节) 指向常量池中的一个 CONSTANT_Class_info,表示父类的全限定名。
9 接口计数器 u2 (2 字节) 表示该类实现的接口数量。
10 接口索引表 u2 [] (每项 2 字节) 按顺序指向常量池中的 CONSTANT_Class_info,表示所有实现的接口。
11 字段计数器 u2 (2 字节) 表示该类中声明的字段数量(包括静态和非静态)。
12 字段表 (Fields) field_info [] (变长) 描述每个字段的名称、类型、修饰符及可选属性(如 ConstantValue)。
13 方法计数器 u2 (2 字节) 表示该类中声明的方法数量(包括构造器 <init> 和静态初始化器 <clinit>)。
14 方法表 (Methods) method_info [] (变长) 描述每个方法的名称、参数、返回类型、修饰符及字节码指令(Code 属性)。
15 属性计数器 u2 (2 字节) 表示该类级别的属性数量。
16 属性表 (Attributes) attribute_info [] (变长) 存放该类级别的附加信息(如 SourceFile、InnerClasses、BootstrapMethods 等)。

版本号(Minor Version & Major Version)

字段名 大小 位置(相对偏移) 说明
次版本号 u2 4-5 编译该 Class 文件所使用的 JDK 副版本号。通常为 0,除非使用特定预览特性。
主版本号 u2 6-7 编译该 Class 文件所使用的 JDK 主版本号。例如: - 52 → Java 8 - 55 → Java 11 - 61 → Java 17 - 65 → Java 21

作用:

JVM 启动时会校验主版本号是否小于等于当前 JVM 支持的最高版本。若高于当前 JVM 版本,会抛出 UnsupportedClassVersionError。次版本号通常为 0,但在某些特殊预览功能下可能非零。

访问标志(access_flags)

字段名 大小 位置(相对偏移) 说明
访问标志 u2 8-9 使用位掩码标识该类或接口的访问权限和特性。

常用标志位(可组合):

标志名称 十六进制值 含义
ACC_PUBLIC 0x0001 声明为 public,可从包外访问
ACC_FINAL 0x0010 声明为 final,不允许被继承
ACC_SUPER 0x0020 使用 invokespecial 指令时需增强语义(JDK 1.0.2+ 必须设置)
ACC_INTERFACE 0x0200 这是一个接口,而非类
ACC_ABSTRACT 0x0400 声明为 abstract,不能实例化
ACC_SYNTHETIC 0x1000 由编译器生成的合成类(非源码显式定义)
ACC_ANNOTATION 0x2000 声明为注解类型(隐含 ACC_INTERFACE)
ACC_ENUM 0x4000 声明为枚举类型
ACC_MODULE 0x8000 这是一个模块(Java 9+)

示例:

  • public class Hello → 标志值 = 0x0021(ACC_PUBLIC + ACC_SUPER)
  • public interface MyInterface → 标志值 = 0x0601(ACC_PUBLIC + ACC_INTERFACE + ACC_ABSTRACT)
  • enum Color → 标志值 = 0x4031(ACC_PUBLIC + ACC_FINAL + ACC_SUPER + ACC_ENUM)

常量池结构

前置字节类型说明

  • u1:无符号 1 字节(8bit)
  • u2:无符号 2 字节(16bit)
  • u4:无符号 4 字节(32bit)
  • u8:无符号 8 字节(64bit)

每个常量项由 tag(u1,标识类型)开头,后接该类型专属的成员字段。下表按「常量类型」列出,成员字段用 字段名 (类型) 形式紧凑书写:

常量名 业务释义 成员字段(类型)
CONSTANT_Utf8_info UTF8 编码原始字符串(底层字符串存储载体) tag (u1) = 1
length (u2):后续 bytes 的字节长度
bytes (u1[length]):变体 UTF-8 字节流,存类名、字段名、方法名、描述符等
CONSTANT_Integer_info int 整型字面量 tag (u1) = 3
bytes (u4):大端存储的 4 字节 int
CONSTANT_Float_info float 单精度浮点字面量 tag (u1) = 4
bytes (u4):大端存储的 IEEE754 单精度值
CONSTANT_Long_info long 长整型字面量 tag (u1) = 5(占 2 个索引槽)
bytes (u8):大端存储的 8 字节 long
CONSTANT_Double_info double 双精度浮点字面量 tag (u1) = 6(占 2 个索引槽)
bytes (u8):大端存储的 IEEE754 双精度值
CONSTANT_Class_info 类 / 接口的符号引用 tag (u1) = 7
name_index (u2):指向 Utf8,内容为全限定类名(如 java/lang/String)
CONSTANT_String_info String 字符串字面量(代码中的双引号字面量) tag (u1) = 8
string_index (u2):指向 Utf8,读取原始字符串
CONSTANT_Fieldref_info 成员字段的符号引用 tag (u1) = 9
class_index (u2):指向 Class,标记字段归属的类
name_and_type_index (u2):指向 NameAndType,存字段名 + 类型描述符
CONSTANT_Methodref_info 普通类成员方法的符号引用 tag (u1) = 10
class_index (u2):指向 Class,标记方法归属的类
name_and_type_index (u2):指向 NameAndType,存方法名 + 方法签名
CONSTANT_InterfaceMethodref_info 接口定义方法的符号引用 tag (u1) = 11
class_index (u2):指向 Class,标记方法归属的接口
name_and_type_index (u2):指向 NameAndType,存接口方法名 + 签名
CONSTANT_NameAndType_info 名称 + 类型签名(不存完整符号引用,做通用拆分复用) tag (u1) = 12
name_index (u2):指向 Utf8,字段 / 方法名(如 age、toString)
descriptor_index (u2):指向 Utf8,类型描述符(字段 I 代表 int;方法 ()V 代表无参无返回)

JDK7+ 新增常量

旧版 class 文件中不存在以下常量:

常量名 业务释义 成员字段(类型)
CONSTANT_MethodHandle_info 方法句柄(Lambda / 反射动态调用的底层载体) tag (u1) = 15
reference_kind (u1):引用类型,取值 1~9,决定句柄调用语义
reference_index (u2):指向常量池其他项,具体指向哪种由 reference_kind 决定
CONSTANT_MethodType_info 方法类型签名(仅描述参数 + 返回值类型) tag (u1) = 16
descriptor_index (u2):指向 Utf8,存方法描述符(如 (II)V)
CONSTANT_InvokeDynamic_info 动态调用点(Lambda 表达式、invokedynamic 指令的底层依赖) tag (u1) = 17
bootstrap_method_attr_index (u2):指向属性区 BootstrapMethods 表的索引
name_and_type_index (u2):指向 NameAndType,存动态方法名 + 签名

字段表

字段表整体结构为「计数器 + 数组」,即先读 fields_count,再循环读取相应数量的 field_info 条目。每个 field_info 的字段如下:

字段名 字节类型 数量 详细说明
access_flags u2 1 访问标志:public/private/protected/static/final/volatile/transient 等
name_index u2 1 指向常量池 Utf8,存字段简单名称(如 age、name)
descriptor_index u2 1 指向常量池 Utf8,存字段类型描述符(I = int、Ljava/lang/String; = 字符串)
attributes_count u2 1 该字段附加属性的数量
attributes attribute_info attributes_count 字段属性表,常见 ConstantValue、Synthetic、Deprecated、Signature 等

为什么叫“表”?

在字节码规范中,“表”特指这种计数+数组的变长结构。JVM 解析时,先读取一个 2 字节的计数,然后循环读取指定数量的条目。这种设计使得文件紧凑且易于扩展——你可以在末尾追加新的属性,只要更新计数器即可。

方法表

与方法表结构类似,同样先读 methods_count,再循环读取相应数量的 method_info 条目:

字段名 字节类型 数量 详细说明
access_flags u2 1 访问标志:public/private/protected/static/final/synchronized/native/abstract 等
name_index u2 1 指向常量池 Utf8,存方法名(如 toString、<init> 构造器、<clinit> 静态初始化)
descriptor_index u2 1 指向常量池 Utf8,存方法签名描述符(如 ()V 无参无返回、(II)I 两 int 返 int)
attributes_count u2 1 方法属性数量
attributes attribute_info attributes_count 方法属性表,核心为 Code(字节码指令体),还有 Exceptions、LineNumberTable、LocalVariableTable 等

属性表

属性是 class 文件中最灵活的部分:不同位置可挂载不同属性,且允许自定义扩展。

属性名称(出现位置) 属性作用 JDK 版本 核心内部字段
Code(方法表) 存储 Java 方法编译后的字节码指令、操作数栈、局部变量表、异常表 JDK1.0+ max_stack、max_locals、code_length、code[]、exception_table、attributes
ConstantValue(字段表) 为 static final 常量赋值,指向常量池字面量 JDK1.0+ constantvalue_index(指向常量池常量)
Exceptions(方法表) 记录方法 throws 声明的受检异常列表 JDK1.0+ number_of_exceptions、exception_index_table[]
LineNumberTable(Code 属性内) 字节码偏移量 → Java 源码行号映射,调试与异常栈追踪用 JDK1.0+ line_number_table_length、line_number_table[]
LocalVariableTable(Code 属性内) 局部变量表描述:变量名、类型、作用域起止偏移、槽位索引 JDK1.0+ local_variable_table_length、local_variable_table[]
SourceFile(Class 文件顶层) 记录源文件名(如 HelloWorld.java),异常栈显示用 JDK1.0+ sourcefile_index(指向 Utf8)
BootstrapMethods(Class 文件顶层) invokedynamic 动态调用的引导方法列表,配合常量池 tag = 17 使用 JDK7+ num_bootstrap_methods、bootstrap_methods[]
InnerClasses(Class 文件顶层) 内部类 / 外部类注册信息表 JDK1.1+ number_of_classes、inner_classes[]
Synthetic(字段 / 方法 / 类) 标记该成员由编译器自动生成,非用户源码编写 JDK1.1+ 无额外字段(attribute_length = 0)
Deprecated(字段 / 方法 / 类) 标记该成员已废弃,对应 @Deprecated 注解 JDK1.1+ 无额外字段
Signature(类 / 字段 / 方法) 存储泛型签名信息(类型擦除后保留泛型用于反射) JDK5+ signature_index(指向 Utf8)
StackMapTable(Code 属性内) 栈映射帧,JDK6 后字节码校验器类型检查优化用 JDK6+ number_of_entries、entries[]
RuntimeVisibleAnnotations 类/字段/方法 运行时可见注解(@Retention(RUNTIME)),反射可读取 JDK5+
RuntimeInvisibleAnnotations 类/字段/方法 运行时不可见注解(@Retention(CLASS)),仅 class 文件保留 JDK5+

Code 属性(方法体)

这是方法表中最重要的属性,存储了实际的字节码指令。

Code 属性自身的字段:

字段名 字节类型 数量 详细说明
attribute_name_index u2 1 属性名索引,指向常量池 Utf8,固定字符串 "Code"
attribute_length u4 1 属性总长度(不含前 6 字节的 name_index + length 本身)
max_stack u2 1 操作数栈最大深度,方法执行中栈顶不会超过该值
max_locals u2 1 局部变量表所需的最大槽位数(long/double 占 2 槽)
code_length u4 1 字节码指令的字节总长度
code u1 code_length 字节码指令流:JVM 指令序列,每条 = 1 字节操作码 + 可选操作数
exception_table_length u2 1 异常表条目数量
exception_table 异常表条目 exception_table_length 异常处理器表,每条对应一个 try-catch 块
attributes_count u2 1 Code 属性内部嵌套的子属性数量
attributes attribute_info attributes_count Code 的子属性集合(见下)

exception_table 中每条异常表条目的字段:

字段名 字节类型 详细说明
start_pc u2 try 块起始字节码偏移量(包含)
end_pc u2 try 块结束字节码偏移量(不包含)
handler_pc u2 异常处理器(catch 块)的起始字节码偏移量
catch_type u2 捕获异常类型,指向常量池 Class 索引;值为 0 表示捕获所有异常(常用于 finally 语义)

Code 内部常见的子属性:

子属性名 详细说明
LineNumberTable 字节码偏移量 → Java 源码行号的映射,用于调试和异常栈行号显示
LocalVariableTable 局部变量描述表:变量名、类型描述符、作用域起止偏移、槽位索引
StackMapTable 栈映射帧(JDK6+),供类型检查校验器使用,减少运行时校验开销
LocalVariableTypeTable 泛型局部变量签名表(JDK5+),配合类型擦除保留泛型信息