2 Class文件结构
1. 基础概念
1.1 什么是字节码
1.Java 源码 .java经过 javac编译器编译后,生成 .class 字节码文件
2.字节码不是机器码:不能直接给操作系统 CPU 执行,只能被JVM(Java 虚拟机) 解析执行
3.字节码:**二进制文件**,由 16 进制数字组成
2. .class 文件整体结构
字节码文件是严格有序的二进制流,固定顺序 10 大组成部分,缺一不可:
1. 魔数(Magic) 4字节
2. 版本号(Minor + Major) 4字节
3. 常量池(Constant Pool) 变长
4. 访问标志(Access Flags) 2字节
5. 类索引this_calss、父类索引super_class、接口索引集合interfaces
6. 字段表集合(fields) 变长
7. 方法表集合(methods) 变长
8. 属性表集合(attributes) 变长

1)魔数 Magic(4 字节)
固定值:0xCAFEBABE(咖啡宝贝,Java 咖啡文化命名)
作用:JVM 加载文件第一时间校验魔数,不是这个值直接判定不是合法 class 文件,加载失败。
2)版本号(4 字节)
- 前 2 字节:次版本号 Minor Version
- 后 2 字节:主版本号 Major Version
版本对应关系举例: - JDK8 = 52.0
- JDK11 = 55.0
- JDK17 = 61.0
规则:高版本 JVM 能运行低版本字节码;低版本 JVM 不能运行高版本 class。
3)常量池 Constant Pool(字节码最核心、最大部分)
class 文件的资源仓库,存储所有字符串、类名、方法名、字段名、字面量、符号引用。是class文件最复杂的部分。
1.结构:
- 开头 2 字节:常量池容量
constant_pool_count(计数器,实际常量数量 = count-1,下标从 1 开始,0 空置) - 后面连续 N 个常量项,每个常量第一项 1 字节标记常量类型 tag标记
2.常见常量类型 tag标记
- 作用
避免重复存储字符串,复用常量;
所有代码里的类、方法、变量都通过下标引用常量池,大幅压缩 class 体积
示例:代码 String s = "hello",“hello” 存在 Utf8 常量。
4)访问标志 access_flags(2 字节)
二进制位标识当前类的修饰符,如 public、final、interface、abstract、enum
常用标记:
- 0x0001 ACC_PUBLIC:public 类
- 0x0010 ACC_FINAL:final 不可继承
- 0x0200 ACC_INTERFACE:接口
- 0x0400 ACC_ABSTRACT:抽象类
5)类索引、父类索引、接口索引
1. this_class:2 字节,常量池下标,代表当前类;
2. super_class:2 字节,父类索引;所有类父类默认 Object(除 Object 本身);
3. interfaces:接口数组,存储本类实现的所有接口常量池下标。
6)字段表集合 fields
存储当前类所有成员变量(静态变量 + 实例变量):
每个字段结构包含:访问标志、字段名索引、描述符索引、属性表(如常量值、注解)。
描述符规则:
I=int,J=long,D=double,L + 类名;= 对象,[= 数组
例:String name 描述符 Ljava/lang/String;
7)方法表集合 methods(重点,存放方法字节码指令)
每个方法对应一个方法表,结构:
- 访问标志:public/private/static/final 等
- 方法名索引:常量池下标(init 构造、clinit 静态代码块、自定义方法)
- 方法描述符:参数 + 返回值
- 属性表:最重要属性
Code属性,存放方法执行字节码指令
特殊方法:
<init>:实例构造方法,new 对象时调用;<clinit>:静态构造方法,类加载时执行静态变量、静态代码块。
8)属性表 attributes
附加信息,遍布类、字段、方法中,常用属性:
- Code:方法的字节码指令、局部变量表、操作数栈、行号映射;
- LineNumberTable:源码行号与字节码行映射,异常堆栈打印行号依赖它;
- LocalVariableTable:局部变量名称,调试时能看到变量名;
- SourceFile:记录源码文件名(如 Test.java);
- Exceptions:方法声明抛出的异常;
- Signature:泛型信息(泛型编译擦除,靠该属性保留泛型)。
3. 字节码指令
3.1 执行模型:基于栈的虚拟机
JVM 执行方法时依赖两块内存:
- 操作数栈:临时存放计算数据(入栈、出栈运算);
- 局部变量表:存放方法入参、局部变量,数组结构,下标访问。
所有字节码指令都是操作「栈」和「局部变量表」。
3.2 常见字节码指令分类 + 示例
(1)加载 / 存储指令:局部变量 ↔ 操作数栈
iload_0:int 局部变量 0 压入栈(方法第一个参数默认 0 下标)istore_1:栈顶 int 存入局部变量 1aload:引用类型加载astore:引用类型存储
(2)常量入栈指令
bipush 10:数字 10 压栈ldc #2:从常量池 #2 取出字符串 / 数字入栈
(3)运算指令(栈顶弹出两个数计算,结果压回栈)
iadd整数相加isub减法、imul 乘法、idiv 除法
(4)控制跳转指令(if 判断、循环)
if_icmpge:两 int 对比,大于等于则跳转goto无条件跳转return方法正常返回ireturn返回 int、areturn返回对象
(5)对象、数组指令
new #3:创建类(常量池 #3 对应类)invokespecial调用构造方法invokevirtual调用实例普通方法(多态)invokestatic调用静态方法invokeinterface调用接口方法getfield/putfield获取 / 设置实例字段getstatic/putstatic获取 / 设置静态字段
public class Test {
public int add(int a, int b) {
return a + b;
}
}
public int add(int, int);
Code:
0: iload_1 // 局部变量1(a)入栈
1: iload_2 // 局部变量2(b)入栈
2: iadd // 栈顶两数相加,结果入栈
3: ireturn // 返回栈顶int
执行流程:
局部变量表 [0:this, 1:a, 2:b]
- a 压栈 → 栈:[a]
- b 压栈 → 栈:[a,b]
- iadd 弹出两个值,计算 a+b,结果压栈
- ireturn 返回结果
4. 类加载过程与字节码的关系
- 加载:磁盘读取
.class二进制字节流到内存; - 验证:校验魔数、版本、字节码指令合法性,防止恶意破坏字节码;
- 准备:静态变量分配内存、赋默认值;
- 解析:把常量池符号引用转为直接内存地址;
- 初始化:执行
<clinit>静态代码块。
整个过程全部基于解析 class 字节码文件。
5. 字节码的关键特性
5.1 泛型擦除
编译时泛型会被擦除,字节码中只保留原始类型;仅通过 Signature 属性保留泛型元数据,反射才能获取泛型。
List<String> list = new ArrayList<>();
// 字节码里实际是 List
5.2 自动装箱 / 拆箱语法糖
Integer i = 10 字节码自动补充 Integer.valueOf(10)int a = i 补充 i.intValue()
5.3 循环、foreach 语法糖
foreach 遍历数组 / 集合会编译成普通 for 循环 + 迭代器调用,字节码看不到 foreach 关键字。
5.4 字符串拼接 +
JDK8 及以前:字符串 + 编译为 StringBuilder 链式 append;
JDK9+:使用 invokedynamic 动态字符串拼接优化。
5.5 异常 try-catch
字节码中存在异常表 Exception Table,记录捕获范围、异常类型、跳转地址,实现异常拦截。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)