2.2 我是一个Java Class
把 Java Class 拆成 class 文件、运行时类和实例对象,沿加载、链接、初始化与调用追踪状态变化。
学习目标
- 能区分磁盘上的 class 文件、JVM 中的运行时类元数据和堆上的实例对象
- 能沿加载、验证、准备、解析、初始化和首次使用追踪一次类生命周期
- 能用正常、边界和单一故障样本定位版本损坏、符号缺失、初始化循环和类加载器分叉
2.2 我是一个Java Class
本页依据刘欣《码农翻身》(2018 年第 1 版)及出版社公开书目信息,独立重构 2.2 我是一个Java Class。正文、代码、图示、实验和练习都是本课程重新设计的教学材料,不复制原书正文、插图、练习答案或代码。
Java Class 不是一张源代码截图。磁盘上有 class 文件,包含常量池、字段、方法和属性;JVM 加载并链接它,形成运行时类元数据;new 创建的实例对象只保存自己的字段状态,并通过运行时类找到方法和类型信息。把三者混成一个“类”,就无法解释静态初始化、符号解析、类加载器和对象生命周期。
三个会让类生命周期失真的陷阱
六个目录节点到类生命周期证据
陌生警察
↡在类进入运行时前检查 class 文件结构、版本、类型约束和字节码安全性的验证边界。代表验证阶段。它不会执行所有业务逻辑,而是先拒绝魔数、版本、栈图或类型关系不成立的类文件。正常样本要记录验证通过;边界样本改变版本或结构,记录拒绝位置和错误类别。
刺探信息
↡读取常量池、字段和方法等符号信息,并建立运行时可用的静态存储与元数据准备状态。对应准备和符号信息观察。准备阶段给静态字段建立默认值,解析可以提前或按需完成;不要把“看见了符号”误当成“业务初始化已经执行”。
初识虚拟机
↡JVM 将 class 文件转换为可管理的类元数据、方法信息和对象分配规则的运行时边界。要求同时看类加载器、方法区/元数据、堆对象和执行栈。它不是一个黑盒“运行 Java”的按钮;每个阶段都有可观察的状态、失败和资源成本。
快乐假期
↡类已完成必要的加载与链接,首次主动使用触发初始化并让静态代码获得业务可见效果的阶段。表示初始化完成后的正常使用路径。若静态初始化抛出异常,后续主动使用可能被拒绝;实验要把首次使用、重复使用和初始化失败分开记录。
真相大白
↡通过对照 class 文件、运行时类元数据和实例对象,确认类型身份、字段状态与方法分派来源。不是打印一个类名就结束。检查对象的实际类、类加载器、代码源、静态字段和实例字段,才能解释“文件已更新但旧实例仍然运行”或“同名类型无法转换”。
2.2 我是一个Java Class
2.2 我是一个Java Class的核心是三层分离和一条状态机:加载文件,验证结构,准备默认状态,解析符号,初始化静态逻辑,最后在实例上执行方法。类文件是交付物,运行时类是 JVM 的定义,实例是某次 new 得到的状态;它们共享名称,却不共享生命周期。
最小类生命周期合同
record ClassObservation(
String binaryName,
String loaderId,
String codeSource,
String phase,
int staticValue,
int instanceValue
) {}
static boolean canUse(ClassObservation observation) {
return observation.phase().equals("initialized")
&& !observation.loaderId().isBlank()
&& !observation.codeSource().isBlank();
}合同要求每一条观测绑定二进制名、类加载器、代码源、生命周期阶段和字段状态。staticValue 在准备阶段可能仍是默认值;instanceValue 必须属于某个实例。若阶段或身份不清楚,先停止调用并补证据。
四步复核一个 Java Class
1. 读取 class 文件并确认身份
保存 class 文件来源、版本、二进制名、常量池摘要和加载器。正常样本从干净类路径加载;边界样本替换一个文件或版本,观察验证与加载错误。
Lab
Class 生命周期实验
只改变一个阶段或加载器条件,观察默认值、初始化值和类型身份的变化。
加载、链接和初始化依次完成
load → verify → prepare(default 0) → init(42) → new(instance=7)
判定
accept:静态值与实例值各有来源
当前样本:正常初始化;保存阶段、类加载器、静态字段、实例字段和异常。
正常、边界与单一故障证据
| 样本 | 只改变的变量 | 预期判定 | 必存证据 |
|---|---|---|---|
| 正常 | 文件、加载器、版本和初始化路径匹配 | 类初始化完成,实例身份可解释 | 文件摘要、阶段、加载器、字段值 |
| 边界 | 延迟解析、重复加载器或默认静态值 | 明确区分默认值、初始化值和类型身份 | 触发点、类加载器、代码源、线程 |
| 单一故障 | 一个版本、符号或静态初始化动作失败 | 在对应阶段拒绝,重启后可重放 | 异常、首个阶段差异、恢复日志 |
故障诊断:先定位阶段,再定位对象
- 文件/验证层:看魔数、版本、常量池和字节码结构;验证失败时不要继续讨论实例字段。
- 链接层:看准备值、符号解析、依赖类和加载器;区分找不到符号、类型不兼容和初始化失败。
- 初始化层:看首次主动使用、静态代码块、线程和异常;检查是否出现循环依赖或“错误后继续使用”。
- 实例层:看实际类、加载器、代码源、静态字段与实例字段;同名不等于同一类型。
如果日志只有“加载成功”,仍不足以证明初始化完成;如果实例方法能调用,也不能反推出所有静态依赖都已正确初始化。诊断要绑定阶段、身份和状态三件事。
最小类证据包与反例
证据包包含 class 文件哈希、编译目标、二进制名、类加载器、代码源、验证/解析记录、静态默认值与最终值、初始化线程、实例身份、异常和干净重放结果。涉及插件时,还要保存加载器层级与依赖版本。
反例一是准备阶段读取静态字段得到默认值,业务把它当成初始化后的配置;反例二是两个加载器各自定义同名类,实例强制转换失败。修复后不要只清空应用缓存,要重启或隔离类加载器并重新保存生命周期轨迹。
术语表
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 陌生警察
在运行前检查 class 文件结构、版本、类型约束和字节码安全性的验证边界。
- 刺探信息
观察常量池、字段、方法和静态存储准备状态的符号信息阶段。
- 初识虚拟机
连接 class 文件、类元数据、堆对象和执行栈的 JVM 运行时边界。
- 快乐假期
类完成初始化并进入正常主动使用的阶段,同时需要验证失败路径。
- 真相大白
对照文件、运行时类和实例,确认类型身份与字段状态的复核动作。
练习
练习
问题 1: 类已经被加载,但静态字段仍是默认值。你如何区分准备阶段和初始化阶段?
问题 2: 两个插件都定义同名类,为什么实例不能互相转换?
问题 3: 静态初始化抛出异常后,下一次主动使用仍然失败。诊断时先查什么?
本页小结
2.2 我是一个Java Class 的关键不是把 class 文件、运行时类和实例对象叫成同一个“类”,而是沿加载、验证、准备、解析、初始化和首次使用分别取证。完成标准是能解释默认值与初始化值、同名类与类加载器的关系,并从干净 JVM 重放版本、符号和初始化故障。