类加载器的核心作用是将.class字节码转换为JVM可执行的Class对象,并强制实施双亲委派机制以保障安全与类型兼容性;它禁止用户直接new Class,要求所有类必须经defineClass()流程加载,且验证、准备、初始化不可绕过。

类加载器的核心作用是把 .class 字节码变成 JVM 能执行的 Class 对象,同时决定“谁有资格加载哪个类”——它不只干活,更管权限和边界。
为什么不能直接 new Class?
因为 JVM 不允许用户手动构造 Class 对象;它必须由类加载器通过 defineClass() 从字节码生成。这是安全底线:所有类都得“走流程”,不能绕过验证、准备、初始化这些环节。
- 你写
new MyClass(),JVM 发现MyClass还没加载,就会调用当前线程上下文的ClassLoader去找MyClass.class - 如果类已加载(在方法区存在对应
Class实例),就直接复用;否则触发完整加载流程 - 自定义类加载器若没调用
super.defineClass(),哪怕字节码正确,也会抛NoClassDefFoundError
双亲委派不是建议,是强制链路
当你调用 ClassLoader.loadClass("java.lang.String"),实际执行路径是:AppClassLoader → ExtClassLoader → BootstrapClassLoader,最后由 C++ 实现的启动类加载器从 rt.jar 加载——你自己的类加载器根本没机会碰这个类。
- 破坏双亲委派(比如重写
loadClass()跳过parent.loadClass())会导致LinkageError:同一个类被不同加载器加载两次,JVM 认为它们是两个不兼容类型 - 常见破例场景:JDBC 驱动(
ServiceLoader用Thread.currentThread().getContextClassLoader())、OSGi、热部署——但都需明确承担隔离风险 - 验证阶段(
Verification)由加载它的那个类加载器负责,不是“上级代劳”,所以自定义加载器必须自己确保字节码合法
自定义类加载器最容易错在哪?
多数人以为只要重写 findClass(String name) 就够了,其实漏掉了关键一环:资源定位逻辑必须和双亲委派对齐。
立即学习“Java免费学习笔记(深入)”;
- 错误写法:
new CustomClassLoader().loadClass("com.example.Foo")—— 没设置父加载器,导致连java.lang.Object都找不到,直接NoClassDefFoundError - 正确写法:构造时传入
super(ClassLoader.getSystemClassLoader()),让非自定义类仍走系统链路 -
defineClass()返回的Class对象,其getClassLoader()就是当前实例,别误以为会自动绑定到系统加载器 - 如果从网络加载,记得处理
SecurityManager策略(虽然 JDK 17+ 默认移除了,但某些容器环境仍启用)
真正难的不是写一个能加载类的加载器,而是想清楚:这个类该不该由它加载?加载后和现有类是否兼容?有没有隐式依赖其他类加载器已加载的类型?这些问题一旦出错,堆栈里不会告诉你哪行代码错了,只会报一句 ClassNotFoundException 或更隐蔽的 IncompatibleClassChangeError。








