我们用 “中医抓药” 的完整场景,结合具体代码和药方示例,详细拆解 JVM 类加载的每个步骤,特别明确:医生开的 “药方就是类”,按方子抓出的 “药就是 new 出来的对象” ,同时融入类加载器和 Class 对象的类比,让每个环节都像看一场抓药全过程一样清晰:
核心对应与示例
• 药方 = 类(Class) :下面是 “祛湿药方”(对应User类代码),纸上写的药材、剂量、用法是 “模板”,本身不是药。
• 按方子抓的药 = 对象(Object) :用 “new”(抓药动作)把方子变成的真实药包,是 “模板的具体产物”。
示例:User 类代码与对应的 “祛湿药方”
User 类代码(相当于 “祛湿药方”)
public class User {
// 实例变量(每副药的具体药材)
String name; // 对应“茯苓”
int age; // 对应“白术”
// 静态变量(所有药包都要加的通用辅料)
static String type = "祛湿方"; // 对应“生姜3片”
// 静态代码块(抓药前的准备步骤)
static {
System.out.println("抓药前先预热砂锅"); // 对应“预热砂锅10分钟”
}
// 构造方法(抓药时的配伍方法)
public User(String name, int age) {
this.name = name;
this.age = age;
}
// 实例方法(用药方法)
public void takeMedicine() {
System.out.println(name + "服用" + type + ",成分:茯苓" + age + "g");
}
}
对应的 “祛湿药方”(纸质版)
【祛湿药方】
1. 每副药含:
- 茯苓 [对应name]
- 白术 [对应age] (剂量按抓药时要求)
2. 所有抓此药者,必加:
- 生姜3片 [对应static String type = "祛湿方"]
3. 抓药前必须:
- 用文火预热砂锅10分钟 [对应static代码块]
4. 配伍方法:
- 先称茯苓,再称白术,混合后加入生姜 [对应构造方法]
5. 服用方法:
- 每日1副,温水送服 [对应takeMedicine()方法]
角色设定:类加载器 = 药房里的三种药师
• 药房总管(启动类加载器) :穿深蓝色长袍,戴方巾,坐在药房最里间的红木桌后,负责管理 “国家统一印发的标准药方”(对应 JVM 核心类库,比如java.lang.String这种基础类),只有坐堂药师能直接找他对接。
• 坐堂药师(扩展类加载器) :穿青色长衫,在药房前厅的诊桌坐诊,负责处理 “从各地名医那里收集的特色药方”(对应扩展类库),同时管理抓药学徒,遇到拿不准的方子会请教总管。
• 抓药学徒(应用类加载器) :穿灰色短褂,在药房柜台前忙活,直接接过顾客手里的方子(比如上面的 “祛湿方”—— 对应User类代码),遇到问题先问坐堂药师。
第一步:加载(Loading)—— 学徒接方(类),建档存档
• 场景:你拿着手写的 “祛湿方”(这张药方就是 User 类)走进药房,抓药学徒立刻迎上来接过方子。他先把方子铺平在操作台(这张 “类” 的原件就是硬盘上的User.class文件),然后拿出宣纸和毛笔,小心翼翼地把方子内容抄了一份(这就是Class对象,相当于 “药方底单”),抄完后在底单右下角写上自己的名字 “学徒李四”,接着把底单放进标着 “祛湿类” 的档案盒(对应方法区的类信息存储区)。
• 特殊情况:如果你递过去的是一张印着 “国家药典标准方” 的方子(比如治疗风寒的基础类,对应java.lang.String),学徒会赶紧用托盘托着方子,快步走到里间请总管处理 —— 总管核对后,在底单上签上自己的名字,再让学徒拿回前厅(这就是 “双亲委派机制”:先让父加载器尝试加载)。
• 类比 JVM:
◦ 类加载器就像不同级别的药师,按职责范围接收并处理 “方子(类)”。
◦ Class对象作为 “底单”,从加载阶段就和类信息绑定,记录着 “谁加载的这张方子(类)”。
第二步:验证(Verification)—— 分级核对药方(类),确保靠谱
• 场景:学徒李四先自己核对方子(User 类):有没有医生的签名(类文件的规范标识,对应class关键字和语法结构)?药材名称有没有明显写错(比如把 “茯苓” 写成 “伏苓”—— 对应代码中的语法错误,如少写分号)?剂量是不是在安全范围(比如 “附子 3 克” 是合理的,写 “30 克” 就有问题 —— 对应代码中的逻辑错误,如静态变量类型不匹配)?
• 如果方子上有 “巴豆”“附子” 这类毒性药材(对应类中可能存在的安全风险,如未经授权的系统调用),学徒会捧着方子去找坐堂药师:“师父,您看这方子能用吗?” 坐堂药师会戴上老花镜,对照《中药毒性手册》(相当于 JVM 规范)再核对一遍,确认剂量和配伍没问题后才点头。
• 要是遇到标着 “绝密” 的宫廷秘方(对应 JVM 核心类,如java.lang.Object),坐堂药师会带着学徒一起去请教总管,总管会调出存档的标准版本比对,确保方子没被篡改(就像 JVM 验证核心类库的安全性)。
• 类比 JVM:验证阶段就像 “多级质检”,不同级别的加载器按严格程度检查类文件,避免 “无效方子(错误的类)” 进入后续流程。
第三步:解析(Resolution)—— 翻译药方(类)里的字迹,标注位置****
• 场景:方子(User 类)通过验证后,学徒发现医生写了不少 “天书”:“川军 3g”(对应代码中的System.out,是符号引用)、“二花 5g”(对应takeMedicine()方法名)。这些都是中医的速写代号,普通人看不懂,必须翻译成通用名。
• 学徒拿出《中医代号速查手册》(相当于 JVM 的符号引用对照表),逐条翻译:“川军” 其实是 “大黄”(System.out对应JVM 中的输出流地址),“二花” 是 “金银花”(takeMedicine()对应方法区的内存地址)。
• 翻译完后,他还要在底单(Class对象)上标注每种药材的存放位置:“大黄在西柜第 3 层左数第 2 格”(System.out的实际内存地址)、“金银花在东柜第 1 层右数第 5 格”(takeMedicine()的实际内存地址)。
• 要是遇到手册上查不到的代号(比如医生自创的简称,对应代码中的动态绑定方法),学徒会去问坐堂药师,药师再查更全的资料(对应动态解析,有些引用在运行时才确定)。
• 类比 JVM:解析阶段把类中的 “符号引用” 翻译成 “直接引用”,就像把 “药材代号” 换成 “具体存放位置”,确保后续抓药(创建对象)时能精准找到。
第四步:准备(Preparation)—— 备好药方(类)里的通用辅料
• 场景:方子(User 类)上写着 “所有抓这副药的人,都要加生姜 3 片做药引”(对应静态变量static String type = "祛湿方"),这是 “通用辅料”,不管给谁抓这副药(创建多少 User 对象)都得加,属于方子(类)的 “固定要求”。
• 学徒先从仓库抱来一筐生姜(给静态变量分配内存),放在操作台的角落。此时生姜还没清洗、没切片,只是先 “占个位置”(对应静态变量的默认值,比如type默认是null)。
• 他在底单上备注:“已备好生姜(未处理)”(Class对象记录静态变量的内存分配状态)。
• 类比 JVM:准备阶段不为实例变量(比如每副药里的 “茯苓 10g”,对应 User 对象的name和age)分配内存,只处理 “所有对象共用的静态变量”,就像提前备好所有按此方抓的药都需要的通用辅料。
第五步:初始化(Initialization)—— 处理药方(类)里的辅料,执行要求
• 场景:准备工作做完后,学徒要按方子(User 类)的 “具体要求” 处理辅料:
a. 从筐里拿出 3 片生姜,用清水洗干净,切成薄片(对应静态变量赋值:type = "祛湿方",把默认值null改成实际值“祛湿方”)。
b. 方子末尾用红笔写着 “抓药前先把砂锅用文火预热 10 分钟”(对应静态代码块static {}),学徒立刻去灶台边点燃炭火,把砂锅放上去(执行静态代码块,打印 “抓药前先预热砂锅”)。
• 做完这些,他在底单上把 “未处理” 改成 “已切 3 片生姜,砂锅已预热”(Class对象更新静态变量的实际值和初始化状态)。
• 类比 JVM:初始化是类加载的最后一步,会按程序员的代码给静态变量赋 “真正的值”,并执行静态代码块,相当于 “按方子(类)的细节完成所有准备工作”。
加载完成:抓药(用 new 创建对象)
当以上五步全部完成,操作台的方子(User 类)和档案盒的底单(Class对象)都已准备就绪:
• 药材名、位置、剂量清晰明确(解析完成,takeMedicine()方法的地址已确定)。
• 生姜切好了,砂锅预热完了(静态变量type赋值完成,静态代码块执行完毕)。
此时你说 “按这个方子抓一副药”(执行User u = new User("张三", 20)):
• 学徒从西柜 3 层 2 格取出大黄(堆内存为对象分配空间),东柜 1 层 5 格取出金银花,按剂量称好后包成药包 ——这包药就是 new 出来的 User 对象,里面的 “茯苓(张三)、白术(20g)” 就是对象的实例变量name和age。
• 抓药时的步骤(“先称茯苓,再称白术”,对应构造方法的执行)记在一张便签上(栈内存的方法栈帧),包完药就把便签扔掉(构造方法执行完毕,栈帧出栈)。
完整对应表(含代码与药方)
| JVM 概念 | 中医抓药场景细节 | User 类代码对应 |
|---|---|---|
| 类(Class) | 医生开的 “祛湿药方” :写着药材、剂量、用法的纸 | public class User { ... } |
| 对象(Object) | 按方子抓的药包:用 “new”(抓药)得到的真实药品 | new User("张三", 20) |
| 静态变量 | 所有药包都要加的 “生姜 3 片” | static String type = "祛湿方" |
| 静态代码块 | 抓药前必须 “预热砂锅 10 分钟” | static { System.out.println(...); } |
| 实例变量 | 每副药里的 “茯苓、白术”(剂量因人而异) | String name; int age; |
| 构造方法 | 抓药时的 “配伍方法”(先称茯苓再称白术) | public User(String name, int age) { ... } |
| 启动类加载器 | 穿深蓝长袍的总管,处理国家药典标准方(核心类) | 加载java.lang.String等核心类 |
| 应用类加载器 | 穿灰色短褂的学徒,接顾客的 “祛湿方”(自定义类) | 加载User.class文件 |
| 加载阶段 | 学徒接方、抄底单(Class对象)、存档 | User.class被加载到方法区 |
| 初始化阶段 | 切 3 片生姜(type赋值)、预热砂锅(执行静态代码块) | type被赋值为 “祛湿方”,打印预热信息 |
通过代码、药方和抓药场景的三重对应,能清晰看到:JVM 的类加载过程,就是让 “User 类(祛湿方)” 从代码文件变成 “可以随时创建对象(抓药)” 的准备过程。药方(类)是模板,药(对象)是模板的产物,Class对象就是药方底单而类加载的每一步,都是为了让这个模板变得可用 —— 就像你去药房抓药时,从递方子到拿到药包的每一步都清清楚楚~