Java类加载在发生主动引用时才触发完整流程:new实例、调用静态方法、访问非final静态字段、反射Class.forName()(true参数)、子类初始化时父类未初始化则先触发父类初始化。

类什么时候真被加载?不是“写了就加”,是“用到才动”
JVM 懒加载,只有发生主动引用时才触发完整加载+初始化:
-
new 一个实例(如
new UserService()) - 调用静态方法(如
UserService.doInit()) - 访问或赋值非 final 静态字段(
UserService.VERSION可以;但UserService.COUNT是static final int就不算) -
反射 加载(
Class.forName("com.example.User"),注意:带参数true才初始化) - 初始化子类时,若父类未初始化,则先触发父类初始化
记住:只读 static final 基本类型常量、创建数组(User[] arr = new User[5])、子类引用父类静态字段——这些都不触发初始化,只可能触发加载(甚至都不一定)。
五个阶段不是流水线,而是“穿插执行”的精密配合
加载 → 验证 → 准备 → 解析 → 初始化,名字像顺序,实际常交叉。关键细节:
-
加载阶段:只负责把字节码搞进内存、生成
Class对象。此时static int x = 5;的x还是0(默认值) -
准备阶段:给
static变量分配内存并设零值(不是代码里写的值)。public static String s;→ 设为null;public static int i = 123;→ 还是0 -
初始化阶段:才真正执行
<clinit>方法——也就是所有static{}块 +static字段赋值语句的合并体,且父类优先、线程安全(JVM 保证只执行一次)
双亲委派不是“礼貌”,是 JVM 的安全铁律
不是“我先问问爸爸能不能干”,而是“我坚决不干,必须让爸爸先试”。流程如下:
立即学习“Java免费学习笔记(深入)”;
- 你的类加载器收到请求 → 先调
getParent().loadClass(name) - 一路向上,直到 Bootstrap(C++ 实现,无 parent)
- Bootstrap 找不到 → Ext 找 → App 找 → 都找不到 → 子加载器才调
findClass()
好处硬核:防止核心类被篡改(比如你写个 java.lang.String,AppClassLoader 会委托 Bootstrap 加载真正的 rt.jar 版本,你的类根本不会被用);避免重复加载(同一个类被不同加载器加载,就是两个不兼容的 Class 对象)。
getResourceAsStream 路径总错?本质是 ClassLoader 的查找逻辑
路径不是“相对当前类”,而是相对于该 ClassLoader 的资源根路径。常用写法与含义:
-
this.getClass().getResourceAsStream("config.properties")→ 在UserService.class所在包目录下找 -
this.getClass().getResourceAsStream("/config.properties")→ 从 classpath 根目录(如src/main/resources输出后的位置)找 -
Thread.currentThread().getContextClassLoader().getResourceAsStream("logback.xml")→ 推荐!尤其在框架/容器中,它通常指向应用 classpath,更可靠
如果返回 null,别猜,直接打印路径和 classloader:System.out.println("CL: " + cl);System.out.println("URL: " + cl.getResource("xxx"));
看它到底去哪找了。


















