Java类加载由ClassLoader按需自动完成,分加载、验证、准备、解析、初始化五阶段;遵循双亲委派模型,通过classpath定位.class文件,首次主动使用时触发懒加载。

OpenJDK 把 .class 文件加载进 JVM,不是靠某个“开关”或手动命令触发的,而是由类加载器(ClassLoader)在运行时按需自动完成的。你写完代码、编译出 .class 文件后,只要 JVM 启动时能通过 classpath 找到它,并在执行路径中首次引用该类(比如调用其静态方法、创建实例、访问其静态字段),加载就发生了。
Java 进程启动时 classpath 怎么影响加载
JVM 启动时必须知道从哪找 .class 文件,这个路径就是 classpath。它不默认包含当前目录,也不自动扫描子目录 —— 错配 classpath 是“找不到类”的最常见原因。
-
java -cp . Main:只查当前目录,Main.class必须在当前目录下 -
java -cp target/classes com.example.Main:查target/classes目录及其子包路径(如com/example/Main.class) -
java -cp lib/mylib.jar:target/classes Main(Linux/macOS)或lib/mylib.jar;target/classes(Windows):合并多个来源 - 漏掉
-cp且没设CLASSPATH环境变量时,JVM 只查rt.jar和扩展目录,你的类根本不会被看到
类加载实际发生在哪一刻
OpenJDK 遵循“懒加载”原则:一个类只有在**首次主动使用**时才触发加载和初始化。不是 new 的那一刻才开始,而是更早的符号解析阶段。
- 执行
new MyClass()→ 触发MyClass加载(如果尚未加载) - 调用
MyClass.staticMethod()或访问MyClass.CONST→ 同样触发加载 - 但仅声明
MyClass obj;或出现在泛型里(如List)→ 不触发加载 - 注意:
static {}块在“初始化”阶段执行,而“加载”“验证”“准备”在此之前已完成;static final基本类型常量甚至可能被内联,绕过加载
自定义 ClassLoader 怎么绕过默认行为
如果你需要从非标准位置(比如网络、数据库、加密 ZIP)加载 .class,就得写自定义 ClassLoader 子类。但要注意双亲委派机制的约束:
- 重写
findClass(String name),而不是loadClass(String name)—— 后者已内置委派逻辑 - 在
findClass中用getResourceAsStream(name.replace('.', '/') + ".class")读取字节流,再调用defineClass(name, b, 0, b.length) - 直接调用
defineClass会跳过验证,可能导致VerifyError;如需校验,应复用SecureClassLoader或手动调用verify - 避免破坏双亲委派(比如在
loadClass里直接返回自己的类),否则可能引发LinkageError或ClassNotFoundException,尤其在 Web 容器或模块化环境里
怎么确认某个 class 确实被加载了
光看没报错不等于加载成功。OpenJDK 提供了几个低开销手段验证:
- 加 JVM 参数:
-verbose:class,启动时会打印所有被加载的类名及来源(如[Loaded java.lang.Object from /jdk/jre/lib/rt.jar]) - 运行时检查:
Class.forName("com.example.MyClass") != null成立,说明已加载;若抛ClassNotFoundException,说明路径或名称不对 - 调试类加载器链:
MyClass.class.getClassLoader()返回的是实际加载它的ClassLoader实例,可进一步查getParent()看是否走通了 boot → ext → app 链 - 注意:
MyClass.class本身是加载成功的标志,但若它依赖的某个接口或父类加载失败,会在解析阶段报NoClassDefFoundError,而非ClassNotFoundException
真正容易被忽略的是“加载”和“初始化”的分离:类可以被成功加载、验证、准备,但直到第一次主动使用才会走到 static 块和静态字段赋值。如果你依赖某个 static 初始化逻辑(比如注册服务、加载配置),却只做了类引用而没触发使用,它就永远不会执行。











