核心类(如java.lang.string)永远由bootstrap classloader加载,子加载器无法覆盖或重新加载——这是双亲委派的安全底线;其根本原因不是“找不到”,而是jvm在loadclass阶段直接拦截并返回已加载版本,跳过findclass流程。

核心类(如 java.lang.String、java.util.ArrayList)永远由 Bootstrap ClassLoader 加载,子加载器(AppClassLoader 或自定义加载器)无法“识别”它们,不是因为“找不到”,而是因为 JVM 从设计上就禁止子加载器加载或覆盖这些类——这是双亲委派的安全底线。所谓“无法被识别”,通常表现为:你写了同名类、试图用自定义加载器加载核心包、或反射调用失败,但根本原因不是加载失败,而是加载被跳过或被拒绝。
确认是不是真在“加载核心类”
很多排查误区源于误判目标类是否属于核心类:
-
检查全限定名:只有
java.*、javax.*(部分)、sun.*(已弃用)等由 Bootstrap 加载的包才受强保护;com.sun.*或jdk.internal.*不一定走 Bootstrap,需查 JDK 版本和模块路径 - 不要自己写 java.lang.String:编译能过,但运行时不会被加载——JVM 在 loadClass 阶段就会拦截并直接返回 Bootstrap 已加载的版本,你的类根本不会进入 findClass 流程
-
用 -verbose:class 启动 JVM:观察类实际由哪个加载器加载,例如:
[Loaded java.lang.String from /jdk/lib/modules] → 明确归属 Bootstrap
检查类加载器链路是否被意外切断
子加载器“看不见”核心类,往往是因为它没走标准委派路径,导致父加载器根本没被调用:
- 重写了 loadClass 但没调用 super.loadClass():这是最常见破坏点。若你在自定义加载器中直接 override loadClass 并只调用 findClass,就跳过了委派逻辑,此时连 java.lang.Object 都会报 ClassNotFoundException
- 显式传入 null 父加载器:构造自定义 ClassLoader 时,若 new MyLoader(null),则 parent 为 null,loadClass 中的委派分支失效,后续只能靠 findBootstrapClassOrNull 尝试,而它只查核心路径
- 线程上下文类加载器(TCCL)被错误设置:比如 Servlet 容器中把 TCCL 设为 WebAppClassLoader,而某框架(如 JAX-WS)又依赖 TCCL 加载 javax.xml.bind 类——若该类在 JDK 8 中属扩展类,在 JDK 11+ 中已被移除,就会因委派链断裂而找不到
验证核心类是否真的“不可见”
所谓“不可见”,本质是类加载器作用域隔离,而非字节码缺失。可快速验证:
-
在自定义加载器实例上调用 getClassLoader().loadClass("java.lang.String"):应成功返回 Class 对象,且
getClassLoader()返回 null(即 Bootstrap) -
尝试 getClassLoader().loadClass("your.custom.String")(假设你定义了同名类):会成功加载,但运行时若与 java.lang.String 类型混用(如方法参数),会抛
LinkageError,因为 JVM 拒绝链接不同加载器加载的同名核心包类 - 用 jcmd 或 jstack 查当前线程的 TCCL:确认执行上下文是否意外切换到了无父委派能力的加载器
典型错误场景与修复建议
不复杂但容易忽略:
- SPI 服务加载时手动 new URLClassLoader 并设 parent=null:应显式传入 Thread.currentThread().getContextClassLoader() 作为 parent,确保委派链完整
-
OSGi 或模块化环境(JPMS)中未导出 required package:即使委派正常,模块系统会阻止跨模块访问,需在 module-info.java 中声明
requires java.base; -
混淆工具(ProGuard / R8)误删了核心类引用:不是加载问题,而是字节码被裁剪,需配置 keep 规则保留
java.**相关签名










