loadClass是类加载入口,负责委派、缓存检查和兜底;findClass仅负责查找字节码并调用defineClass生成Class对象,不委派、不查缓存、不解析。

自定义 ClassLoader 时,findClass 和 loadClass 的职责不同、调用时机不同、变量作用范围也不同——关键不在于“谁改了哪个变量”,而在于它们各自在双亲委派链中的定位与协作逻辑。
loadClass 是入口门面,负责委派+缓存+兜底
loadClass(String name) 是类加载的公开入口,JVM 调用它启动整个加载流程。它的默认实现(java.lang.ClassLoader.loadClass)包含三步:
- 先检查该类是否已被加载(
findLoadedClass(name)),这是 JVM 内部维护的已加载类缓存,与你的子类无关; - 若未加载,且存在父加载器,则委托父加载器
loadClass(双亲委派); - 父加载失败(抛
ClassNotFoundException),才调用本类的findClass(name)尝试自己加载。
注意:loadClass 自身不解析字节码、不生成 Class 对象,它只是流程控制器。你通常不应重写它(除非要打破委派,如热部署场景),而应让父类逻辑走完,再由 findClass 承担实际加载工作。
findClass 是真正干活的,只负责“找字节码+defineClass”
findClass(String name) 是模板方法,由子类实现,语义明确:给定类名,返回对应的字节码(byte[]),然后调用 defineClass(name, b, off, len) 把字节码转为 Class 实例。
- 它不查缓存、不委派、不处理链接(
resolve)——这些都由loadClass或后续流程完成; - 它的参数
name是二进制名称(如"java.lang.String"),需按约定映射到资源路径(如"java/lang/String.class"); - 若找不到对应字节码,必须抛
ClassNotFoundException,不能返回null,否则loadClass会 NPE。
变量作用域:没有共享的“加载状态变量”,但有隐式依赖
你无法通过成员变量在 loadClass 和 findClass 之间传递“当前正在加载哪个类”之类的状态——因为:
-
loadClass可能被并发调用,多个线程同时加载不同类; -
findClass是loadClass在委派失败后同步调用的,二者在同一线程栈中,但无共享上下文对象; - JVM 级别的类缓存(
findLoadedClass结果)、双亲引用(parent字段)、类加载器标识(getClassLoader())才是真实共享的“变量”。
如果你需要传参(比如指定 classpath 目录或版本号),应设计为构造时注入的 final 字段,而非运行时动态设值。
常见误区:别在 findClass 里调用 loadClass
在 findClass 中手动调用 this.loadClass(...) 极易引发无限递归或委派混乱:
- 若该类尚未加载,
loadClass会再次走到findClass,死循环; - 若想加载依赖类(如当前类引用了
com.example.Helper),应让 JVM 在解析阶段自动触发其loadClass,而不是你在findClass中主动加载——那是链接(resolve)阶段的事; - 真需提前加载依赖,应在
defineClass后、返回前,用Class.forName(name, false, this)触发(false表示不初始化),但仍建议交由 JVM 自动处理。


















