java内置三类类加载器:bootstrapclassloader加载$java_home/jre/lib/rt.jar等核心类;extensionclassloader(jdk 9+废弃)加载/ext目录jar;appclassloader加载-cp或classpath路径的业务类。

Java里有哪些内置类加载器?各自负责什么路径
Java运行时有三类内置类加载器,它们不是平级关系,而是构成父子委派链:启动类加载器(BootstrapClassLoader)→ 扩展类加载器(ExtensionClassLoader,JDK 9+ 已废弃)→ 应用程序类加载器(AppClassLoader)。
它们的分工很明确:
-
BootstrapClassLoader:由C++实现,不继承ClassLoader,因此在Java代码中调用getClassLoader()会返回null;它加载$JAVA_HOME/jre/lib/rt.jar等核心类(如java.lang.String、java.util.ArrayList) -
ExtensionClassLoader:Java实现,父加载器为BootstrapClassLoader;JDK 8及之前加载$JAVA_HOME/jre/lib/ext下jar包;JDK 9+模块化后该加载器已移除,相关功能由PlatformClassLoader替代(但开发者通常无需直接操作) -
AppClassLoader:也叫SystemClassLoader,父加载器是ExtensionClassLoader(或JDK 9+的PlatformClassLoader);它加载-cp或CLASSPATH指定路径下的所有类,也就是你写的业务代码默认由它加载
双亲委派机制是怎么工作的?为什么不能绕开它
双亲委派不是设计选择,而是安全刚需——它的核心逻辑就藏在ClassLoader.loadClass(String name)源码里:先检查是否已加载(findLoadedClass),没加载则委托父加载器(parent.loadClass),父为null时交由BootstrapClassLoader尝试;全部失败才调用findClass(name)自己加载。
这么做的关键原因:
立即学习“Java免费学习笔记(深入)”;
- 防止恶意覆盖核心类:比如你写一个
java.lang.String放在classpath里,AppClassLoader不会自己加载,而是层层上抛给BootstrapClassLoader——它只认rt.jar里的版本,你的类根本进不去 - 保证类的唯一性:同一个
com.example.User类,无论被哪个子加载器发起请求,最终都由同一加载器(通常是AppClassLoader)定义,避免ClassCastException(例如User被两个不同加载器加载后,实例无法互相赋值) - 避免重复加载:委派链天然去重,上层已加载,下层直接复用
注意:Thread.currentThread().getContextClassLoader()常被用来打破委派(如SPI机制),但这属于“有控制地绕开”,不是随意跳过——它仍依赖父加载器先尝试,失败后才用上下文加载器兜底。
自定义ClassLoader要重写哪些方法?常见错误有哪些
自定义加载器必须继承ClassLoader,但**不能重写loadClass**(否则破坏双亲委派),正确做法是覆盖findClass(String name),并在其中完成字节码读取和defineClass调用。
典型错误和实操要点:
- 直接重写
loadClass并删掉super.loadClass调用 → 导致String等基础类加载失败,JVM直接报NoClassDefFoundError - 在
findClass里用new FileInputStream硬编码路径 → 缺乏资源关闭、异常处理,且无法支持jar包内class或网络加载 - 调用
defineClass后没做resolveClass→ 类虽定义成功,但未链接,首次使用时可能抛UnsatisfiedLinkError或IncompatibleClassChangeError - 忽略
getPackage和getResource等配套方法 → 导致Class.getResource("/config.txt")找不到资源,尤其在Web容器或OSGi场景下容易出问题
一个最小可用示例片段:
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = loadByteCode(name); // 从文件、网络、加密流等读取
if (data == null) throw new ClassNotFoundException(name);
Class<?> clazz = defineClass(name, data, 0, data.length);
resolveClass(clazz); // 关键:触发链接
return clazz;
}
JDK 9+ 的变化对类加载器有什么实际影响
JDK 9引入模块系统(JPMS)后,ExtensionClassLoader被废弃,BootstrapClassLoader和AppClassLoader之间新增了PlatformClassLoader(对应java.base等平台模块),而AppClassLoader现在只负责应用模块。
对开发者的真实影响:
-
ClassLoader.getSystemClassLoader()返回的仍是AppClassLoader,但它的父加载器不再是ExtensionClassLoader,而是PlatformClassLoader;调用getParent().getParent()会得到null(因为PlatformClassLoader的父是BootstrapClassLoader,不可见) -
-Djava.ext.dirs参数彻底失效,$JAVA_HOME/jre/lib/ext目录被忽略;扩展机制改用模块声明(requires)或--add-modules启动参数 - 如果你用
URLClassLoader动态加载jar,需注意模块边界:非模块化jar可正常加载,但若它依赖某个模块(如java.sql),而你的主模块没声明requires java.sql,运行时会报NoClassDefFoundError,而非编译期错误
最易被忽略的一点:模块化后,ClassLoader.getResources("META-INF/MANIFEST.MF")可能返回空迭代器——因为模块系统会过滤非本模块可见的资源,调试时别只盯着类加载,还要查模块导出(exports)和开放(opens)配置。










