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.常量池(ConstantPool) 变长4.访问标志(AccessFlags)2字节5.类索引this_calss、父类索引super_class、接口索引集合interfaces6.字段表集合(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获取 / 设置静态字段
publicclassTest{publicintadd(inta,intb){returna+b;}}publicintadd(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=newArrayList<>();// 字节码里实际是 List5.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,记录捕获范围、异常类型、跳转地址,实现异常拦截。
