掌握类加载器层次结构的关键在于理解“请求—委托—落点”链路:bootstrap加载核心类(如string)且getclassloader()返回null;extclassloader加载jre/lib/ext下类;appclassloader加载classpath类;自定义加载器通过重写loadclass可观察双亲委派全过程。

掌握类加载器层次结构的关键,不在于死记硬背三类系统加载器的名称,而在于看清字节码从哪来、被谁加载、为何走特定路径——可视化本质是还原“请求—委托—落点”的链路逻辑。
看懂三类系统加载器的实际加载范围
启动类加载器(Bootstrap)用C++实现,不继承ClassLoader,因此getClassLoader()返回null。它加载的是sun.boot.class.path指定路径下的核心类,比如java.lang.String、java.util.ArrayList等。可通过以下代码快速验证:
-
System.getProperty("sun.boot.class.path")查看其实际扫描路径 -
String.class.getClassLoader()返回null,印证其非Java对象
识别扩展与应用加载器的真实分工
扩展类加载器(ExtClassLoader)和应用程序类加载器(AppClassLoader)都是ClassLoader的子类,由Java编写,且存在明确的父子委托关系:
- ExtClassLoader 加载
java.ext.dirs目录(如jre/lib/ext)中的JAR - AppClassLoader 加载
classpath(含-cp或CLASSPATH环境变量)下的类 - 执行
MyClass.class.getClassLoader(),99%情况下返回的就是AppClassLoader实例
用自定义加载器暴露委托链真实走向
写一个简单自定义加载器并重写loadClass,在关键节点打印调用栈,能直观看到双亲委派如何逐级向上:
- 重写
loadClass(String name, boolean resolve),开头加System.out.println("【" + this + "】尝试加载: " + name) - 显式调用
super.loadClass(name, resolve)前先打印父加载器信息 - 加载一个
java.lang.Object时,会依次输出App → Ext → Bootstrap的尝试过程,最后Bootstrap成功返回 - 加载一个自定义类
com.example.Foo时,前三层都返回null,最终由自定义加载器自己调用defineClass完成
变量加载路径取决于其所属类的加载器
所谓“复杂变量”的加载路径,其实由该变量所在类的加载器决定,而非变量本身:
- 静态变量
public static final String CONFIG = "prod":若所在类由AppClassLoader加载,则该常量值随类一同进入JVM方法区 - 引用类型变量
private static DataSource ds = new HikariDataSource():其初始化代码在<clinit></clinit>中,执行时机由该类是否被主动使用触发,与加载器无关,但加载器决定了HikariDataSource能否被正确解析 - 数组变量
String[] arr = new String[10]:数组类由JVM直接生成,其元素类型String由Bootstrap加载,因此数组类的getClassLoader()也返回null










