Class 文件内部数据类型/魔数/版本号
Class 文件的数据类型
Class 文件是一种紧凑的二进制格式,使用两种基本数据类型:无符号数和表。
基本数据类型
无符号数
无符号数是 Class 文件的基本数据单位:
| 类型 | 大小 | 说明 |
|---|---|---|
u1 | 1 字节 | 无符号单字节数 |
u2 | 2 字节 | 无符号双字节数(big-endian) |
u4 | 4 字节 | 无符号四字节数 |
Java 使用 Big-Endian(大端序) 存储多字节数据:
u2 值 0x1234 在文件中存储为:
高字节 0x12 → 低字节 0x34
读取时先读高位,再读低位表
表是由多个无符号数或其他表组成的复合结构:
java
// 伪代码描述 Class 文件结构
ClassFile {
u4 magic; // 魔数
u2 minor_version; // 次版本号
u2 major_version; // 主版本号
cp_info constant_pool[]; // 常量池(表)
u2 access_flags; // 访问标识
u2 this_class; // 当前类索引
u2 super_class; // 父类索引
// ... 更多字段
}魔数(Magic Number)
魔数是 Class 文件开头的 4 字节固定值:0xCAFEBABE
Class 文件开头:
┌────────┬────────┬────────┬────────┐
│ CA │ FE │ BA │ BE │
│ 0xCA │ 0xFE │ 0xBA │ 0xBE │
└────────┴────────┴────────┴────────┘
magic = 0xCAFEBABE魔数的来历
这个神奇的数字来自 Café Babe——James Gosling 团队在开发 Java 时的代码命名惯例。当时咖啡是程序员的标配,所以用了咖啡相关的名字。
魔数的作用
魔数用于验证文件是否是一个合法的 Class 文件:
java
// 验证 Class 文件合法性
public class MagicValidator {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("HelloWorld.class");
byte[] header = new byte[4];
fis.read(header);
// 验证魔数
if (header[0] == (byte) 0xCA &&
header[1] == (byte) 0xFE &&
header[2] == (byte) 0xBA &&
header[3] == (byte) 0xBE) {
System.out.println("合法的 Class 文件");
} else {
System.out.println("不是合法的 Class 文件");
}
}
}字节码查看魔数
bash
# 查看 Class 文件的魔数(十六进制)
hexdump -C HelloWorld.class | head -n 1
# 输出:
# 00000000 ca fe ba be 00 00 00 37 00 3d 0a 00 0b 00 27 0a
# └─────┘ └───────────────────────── major version: 55 = JDK 11版本号(Class File Version)
魔数之后是 4 字节版本号:
┌────────┬────────┬────────┬────────┐
│ minor_version │ major_version │
│ (2字节) │ (2字节) │
└────────┴────────┴────────┴────────┘JDK 版本与主版本号对照
| 主版本号 | JDK 版本 | 说明 |
|---|---|---|
| 0x30 = 48 | JDK 1.4 | - |
| 0x31 = 49 | JDK 5 | 引入泛型 |
| 0x32 = 50 | JDK 6 | - |
| 0x33 = 51 | JDK 7 | 动态语言支持 |
| 0x34 = 52 | JDK 8 | Lambda 表达式 |
| 0x35 = 53 | JDK 9 | 模块化 |
| 0x36 = 54 | JDK 10 | - |
| 0x37 = 55 | JDK 11 | - |
| 0x38 = 56 | JDK 12 | - |
| 0x39 = 57 | JDK 13 | - |
| 0x3A = 58 | JDK 14 | - |
| 0x3B = 59 | JDK 15 | - |
| 0x3C = 60 | JDK 16 | - |
| 0x3D = 61 | JDK 17 | - |
| 0x3E = 62 | JDK 18 | - |
| 0x3F = 63 | JDK 19 | - |
| 0x40 = 64 | JDK 20 | - |
| 0x41 = 65 | JDK 21 | - |
bash
# 查看 Class 文件版本
javap -verbose HelloWorld.class | grep "major version"
# 输出:
# major version: 55
# → JDK 11 编译
# 或者用 hexdump
hexdump -C HelloWorld.class | head -n 1
# 00000000 ca fe ba be 00 00 00 37
# └─── 0x37 = 55 = JDK 11版本号的意义
JVM 通过主版本号判断 Class 文件是否可以被当前 JVM 加载:
java
// 如果加载的 Class 文件版本高于 JVM 支持的版本,会报错:
// UnsupportedClassVersionError
UnsupportedClassVersionError: Unsupported major.minor version 55.0
// 55 = JDK 11
// 当前 JVM 可能只支持到 JDK 10(版本 54)Class 文件结构总览
完整的 Class 文件结构:
ClassFile {
u4 magic; // 0xCAFEBABE
u2 minor_version; // 次版本号(通常 0)
u2 major_version; // 主版本号
u2 constant_pool_count; // 常量池计数
cp_info constant_pool[constant_pool_count - 1];
u2 access_flags; // 访问标识
u2 this_class; // 当前类索引
u2 super_class; // 父类索引
u2 interfaces_count; // 接口计数
u2 interfaces[interfaces_count];
u2 fields_count; // 字段计数
field_info fields[fields_count];
u2 methods_count; // 方法计数
method_info methods[methods_count];
u2 attributes_count; // 属性计数
attribute_info attributes[attributes_count];
}访问标识(Access Flags)
access_flags 是一个 16 位的掩码,表示类和接口的访问权限:
| 标识 | 值 | 说明 |
|---|---|---|
ACC_PUBLIC | 0x0001 | public |
ACC_FINAL | 0x0010 | final,不能被继承 |
ACC_SUPER | 0x0020 | 使用新的 invokespecial 语义 |
ACC_INTERFACE | 0x0200 | 接口 |
ACC_ABSTRACT | 0x0400 | abstract |
ACC_SYNTHETIC | 0x1000 | 编译器生成,非源码 |
ACC_ANNOTATION | 0x2000 | 注解类型 |
ACC_ENUM | 0x4000 | 枚举类型 |
ACC_MODULE | 0x8000 | 模块(JDK 9+) |
常见的访问标识组合
| 类型 | 访问标识 |
|---|---|
public class | ACC_PUBLIC | ACC_SUPER = 0x0021 |
class(默认) | ACC_SUPER = 0x0020 |
public abstract interface | ACC_PUBLIC | ACC_INTERFACE | ACC_ABSTRACT |
public final enum | ACC_PUBLIC | ACC_FINAL | ACC_SUPER | ACC_ENUM |
本节小结
Class 文件基础数据类型速查:
| 元素 | 类型 | 说明 |
|---|---|---|
| 魔数 | u4 = 0xCAFEBABE | 文件合法性验证 |
| 版本号 | u2 + u2 | major:minor,JDK 版本对应 |
| 无符号数 | u1/u2/u4 | Class 文件基本数据类型 |
| 表 | 复合结构 | 由无符号数组成 |
| access_flags | u2 | 类/接口的访问权限 |
理解 Class 文件的基本结构,是深入分析字节码的基础。
下一节,我们来看 常量池(计数器/字面量/符号引用)。
