"双亲委派保证核心类不会被覆盖"这句话人人都会背,这篇真实写一个自定义ClassLoader,真刀真枪去尝试加载和伪造 java.lang.String,看JVM到底在哪一步把它拦下来。
上一篇:数组的 hashCode()/equals() 真实验证
自定义一个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 的逻辑。
如果绕开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() 被拦截的那一刻串成动画。