bigywater.tech
Java 内部原理 · 84

双亲委派真实验证:自定义ClassLoader真实无法覆盖java.lang.String

"双亲委派保证核心类不会被覆盖"这句话人人都会背,这篇真实写一个自定义ClassLoader,真刀真枪去尝试加载和伪造 java.lang.String,看JVM到底在哪一步把它拦下来。

loadClass真实委派给Bootstrap
真实验证:自定义ClassLoader.loadClass("java.lang.String")真实拿到的是Bootstrap ClassLoader加载的官方String,不是自己尝试定义的版本
defineClass真实被安全机制拦截
真实验证:强行用defineClass()把自己的字节码注册成"java.lang.String",真实抛出SecurityException: Prohibited package name

上一篇:数组的 hashCode()/equals() 真实验证

真实验证:loadClass() 真实把请求委派给了 Bootstrap

自定义一个ClassLoader,直接调用它的loadClass("java.lang.String"),看真实拿到的是谁加载的类。

ParentDelegationDemo.java class ParentDelegationDemo extends ClassLoader { ParentDelegationDemo(ClassLoader parent) { super(parent); } } ParentDelegationDemo loader = new ParentDelegationDemo(ClassLoader.getSystemClassLoader()); Class<?> loaded = loader.loadClass("java.lang.String");
真实实测
自定义ClassLoader.loadClass("java.lang.String") 真实拿到的类: class java.lang.String
真实的类加载器: null(null代表Bootstrap ClassLoader)
是否真实就是JDK内置的String类: true

真实结果证明了双亲委派链的完整生效:虽然是通过我们自定义的 ParentDelegationDemo 这个 ClassLoader 发起的 loadClass() 调用,但它并没有真的自己尝试去定义一个 String 类。ClassLoader.loadClass() 的默认实现(我们没有重写它)真实会先把请求逐层往上委派——委派给系统类加载器,再往上委派给扩展/平台类加载器,最终委派到最顶层的 Bootstrap ClassLoader。java.lang.String 本身就是 Bootstrap ClassLoader 负责加载的核心类,所以请求真实在最顶层就被满足了,返回的 Class 对象的 getClassLoader() 真实是 null(JVM 用 null 表示 Bootstrap ClassLoader),和 String.class 真实是同一个对象。整个过程中,我们自定义的 ParentDelegationDemo 从未真正执行过加载 String 的逻辑。

真实验证:强行 defineClass() 会被安全机制拦截

如果绕开loadClass()的委派逻辑,直接调用更底层的defineClass(),试图把自己的字节码强行注册成"java.lang.String",会发生什么。

ParentDelegationDemo.java byte[] fakeBytes = /* 任意一段合法的.class字节码 */; defineClass("java.lang.String", fakeBytes, 0, fakeBytes.length);
真实实测
真实捕获 SecurityException: Prohibited package name: java.lang
双重保护机制。这里绕开了 loadClass() 的委派逻辑,直接调用更底层的 defineClass(),理论上应该能把任意字节码注册成任意名字的类。但真实结果是 JVM 在 defineClass() 内部真实做了一层独立的安全检查:凡是尝试定义包名以 java. 开头的类,不管字节码内容是什么,一律真实抛出 SecurityException,错误信息明确写着"禁止的包名"。这说明双亲委派其实是"两道真实防线"的组合:第一道是 loadClass() 的委派顺序,让请求自然而然地被上层类加载器满足,不给自定义类加载器"抢跑"的机会;第二道是 defineClass() 内建的包名保护,即使有人故意绕过第一道防线,试图给核心包"塞"一个自己的类,也会被真实、直接地拒绝。这两道防线加在一起,才是"核心类不会被覆盖"这句话背后真实、完整的保证机制。

分步演示:一次 loadClass 请求真实走过的路径

反射打印出这台机器上真实的类加载器父链,把 loadClass() 的委派过程和 defineClass() 被拦截的那一刻串成动画。

loadClass("java.lang.String") 沿真实父链一路委派到 Bootstrap,再看 defineClass() 绕过委派时被拦下的那一刻
这一步正在被问到的加载器已经问过、自己不处理的加载器真正满足请求 / 被拦截的那一环

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,双亲委派和安全拦截行为重复运行完全一致。
    • 和两个ClassLoader造出两个不能互相强转的Foo那篇一起看,能更完整理解类加载器体系里"隔离"和"统一委派"这两个看似矛盾、实则互补的设计目标。
    • 没有涉及:如何合法地扩展JDK核心类库(通过 --patch-module 参数,而不是运行时hack)、OSGi等模块化框架如何在双亲委派基础上实现"网状"类加载模型——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号