字节码文件解析
什么是字节码
字节码文件是 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 |
| 生命周期 | 编译时生成,运行时加载 | 运行时动态产生,用于跨进程通信或存储 |
总结:序列化二进制流是 对象数据的快照,字节码文件是 类定义的蓝图。两者都可以在网络传输,但前者用于传递数据,后者用于传递可执行代码。
字节码结构


字节码文件整体结构总览
| 序号 | 数据段名称 | 数据类型(大小) | 说明 |
|---|---|---|---|
| 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) = 1length (u2):后续 bytes 的字节长度bytes (u1[length]):变体 UTF-8 字节流,存类名、字段名、方法名、描述符等 |
CONSTANT_Integer_info |
int 整型字面量 | tag (u1) = 3bytes (u4):大端存储的 4 字节 int |
CONSTANT_Float_info |
float 单精度浮点字面量 | tag (u1) = 4bytes (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) = 7name_index (u2):指向 Utf8,内容为全限定类名(如 java/lang/String) |
CONSTANT_String_info |
String 字符串字面量(代码中的双引号字面量) | tag (u1) = 8string_index (u2):指向 Utf8,读取原始字符串 |
CONSTANT_Fieldref_info |
成员字段的符号引用 | tag (u1) = 9class_index (u2):指向 Class,标记字段归属的类name_and_type_index (u2):指向 NameAndType,存字段名 + 类型描述符 |
CONSTANT_Methodref_info |
普通类成员方法的符号引用 | tag (u1) = 10class_index (u2):指向 Class,标记方法归属的类name_and_type_index (u2):指向 NameAndType,存方法名 + 方法签名 |
CONSTANT_InterfaceMethodref_info |
接口定义方法的符号引用 | tag (u1) = 11class_index (u2):指向 Class,标记方法归属的接口name_and_type_index (u2):指向 NameAndType,存接口方法名 + 签名 |
CONSTANT_NameAndType_info |
名称 + 类型签名(不存完整符号引用,做通用拆分复用) | tag (u1) = 12name_index (u2):指向 Utf8,字段 / 方法名(如 age、toString)descriptor_index (u2):指向 Utf8,类型描述符(字段 I 代表 int;方法 ()V 代表无参无返回) |
JDK7+ 新增常量
旧版 class 文件中不存在以下常量:
| 常量名 | 业务释义 | 成员字段(类型) |
|---|---|---|
CONSTANT_MethodHandle_info |
方法句柄(Lambda / 反射动态调用的底层载体) | tag (u1) = 15reference_kind (u1):引用类型,取值 1~9,决定句柄调用语义reference_index (u2):指向常量池其他项,具体指向哪种由 reference_kind 决定 |
CONSTANT_MethodType_info |
方法类型签名(仅描述参数 + 返回值类型) | tag (u1) = 16descriptor_index (u2):指向 Utf8,存方法描述符(如 (II)V) |
CONSTANT_InvokeDynamic_info |
动态调用点(Lambda 表达式、invokedynamic 指令的底层依赖) |
tag (u1) = 17bootstrap_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+),配合类型擦除保留泛型信息 |



