问题

破坏双亲委派之后,能重写String类吗?

答案

核心结论

不能。即使完全破坏双亲委派机制,也无法重写java.lang.String等JDK核心类。这是由JVM的多重安全机制保障的,而非仅依赖双亲委派模型。

原因分析

1. 包访问权限限制(核心防护)

JVM在ClassLoader.preDefineClass()方法中有严格检查:

// ClassLoader.java 源码
private ProtectionDomain preDefineClass(String name, ProtectionDomain pd) {
    if (!checkName(name))
        throw new NoClassDefFoundError("IllegalName: " + name);

    // 检查是否以 "java." 开头
    if ((name != null) && name.startsWith("java.")) {
        throw new SecurityException(
            "Prohibited package name: " +
            name.substring(0, name.lastIndexOf('.')));
    }
    // ...
}

结果:任何自定义类加载器尝试加载java.*包下的类都会抛出SecurityException

2. 启动类加载器优先权

即使绕过包名检查,双亲委派(未完全破坏的情况下)仍会确保:

// 尝试加载 java.lang.String
ClassLoader customLoader = new CustomClassLoader();
Class<?> stringClass = customLoader.loadClass("java.lang.String");

// 实际结果:由启动类加载器(Bootstrap ClassLoader)加载
System.out.println(stringClass.getClassLoader()); // null(启动类加载器)

3. 类的唯一性判定

JVM判断两个类是否相同的条件:

类相同 = 类的全限定名相同 + 加载它的ClassLoader相同

即使自定义类加载器成功加载了一个名为java.lang.String的类,它与JDK的String也是不同的类

// 假设绕过了所有检查(实际不可能)
Class<?> customString = customLoader.loadClass("java.lang.String");
Class<?> jdkString = String.class;

// customString != jdkString(不同的类)
System.out.println(customString == jdkString); // false

实战验证

尝试1:自定义String类

// 创建 MyClassLoader.java
public class MyClassLoader extends ClassLoader {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        // 破坏双亲委派:自己先加载
        if (name.equals("java.lang.String")) {
            byte[] classData = loadClassData(name);
            return defineClass(name, classData, 0, classData.length);
        }
        return super.loadClass(name);
    }

    private byte[] loadClassData(String name) {
        // 读取自定义的 String.class 文件
        // ...
    }
}

// 测试
public class Test {
    public static void main(String[] args) throws Exception {
        MyClassLoader loader = new MyClassLoader();
        Class<?> clazz = loader.loadClass("java.lang.String");
    }
}

运行结果

Exception in thread "main" java.lang.SecurityException:
Prohibited package name: java.lang

尝试2:修改包名检查(理论分析)

即使通过修改JVM源码或使用native方法绕过包名检查,仍然面临问题:

// 假设成功加载了自定义的 java.lang.String
Class<?> myString = customLoader.loadClass("java.lang.String");

// 问题1:无法替换已加载的官方String
String str = "hello"; // 使用的仍是官方String

// 问题2:类型不兼容
Object obj = myString.newInstance();
String realStr = (String) obj; // ClassCastException!

JVM的多层防护机制

第1层:双亲委派模型
  ↓ 破坏后
第2层:包访问权限检查(java.* 禁止)
  ↓ 绕过后
第3层:类加载器命名空间隔离
  ↓ 绕过后
第4层:安全管理器(SecurityManager)检查
  ↓ 绕过后
第5层:JVM底层的类型检查

为什么需要这么多层防护?

安全性考量

如果允许重写核心类,可能导致:

// 恶意重写的 String 类
package java.lang;

public class String {
    private char[] value;

    public String(String original) {
        // 窃取敏感信息
        sendToHacker(original);
        this.value = original.value;
    }

    private void sendToHacker(String data) {
        // 上传用户数据到黑客服务器
    }
}

稳定性考量

核心类如StringObjectClass等被整个JVM和标准库依赖:

// 如果String被替换,所有依赖String的代码都会出问题
HashMap<String, Object> map = new HashMap<>(); // 可能崩溃
System.out.println("hello"); // 可能崩溃

特殊场景:Instrumentation

唯一合法的核心类修改方式是使用Java Agent的Instrumentation API:

// Java Agent 方式
public class MyAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        inst.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader,
                                  String className,
                                  Class<?> classBeingRedefined,
                                  ProtectionDomain protectionDomain,
                                  byte[] classfileBuffer) {
                if ("java/lang/String".equals(className)) {
                    // 可以修改字节码(仅限于增强,不能破坏原有逻辑)
                    return modifyStringClass(classfileBuffer);
                }
                return null;
            }
        });
    }
}

限制

  • 必须在JVM启动时通过-javaagent参数加载
  • 只能增强现有方法,不能完全替换类
  • 受到严格的权限检查

其他核心类保护

同样受保护的包还有:

java.*          // 核心类库
javax.*         // 扩展类库
sun.*           // 私有实现(不建议使用)
com.sun.*       // 私有实现
jdk.*           // JDK内部实现(JDK 9+)

实战对比:可替换的普通类

// 可以替换自己的类
public class CustomClassLoader extends ClassLoader {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        // 可以替换 com.myapp.MyClass
        if (name.equals("com.myapp.MyClass")) {
            byte[] classData = loadClassData(name);
            return defineClass(name, classData, 0, classData.length);
        }
        return super.loadClass(name);
    }
}

// 应用场景
// 1. 热部署(Tomcat)
// 2. 插件系统
// 3. 代码动态生成(CGLIB、Javassist)

面试要点总结

  1. 核心结论:破坏双亲委派后仍然无法重写String等核心类
  2. 主要原因:JVM的包访问权限检查(java.*包禁止自定义类加载器加载)
  3. 多重防护:双亲委派只是第一层,还有包名检查、类命名空间、安全管理器等
  4. 类的唯一性:即使加载成功,由不同ClassLoader加载的同名类也是不同的类
  5. 合法方式:使用Java Agent的Instrumentation API进行字节码增强(有限制)
  6. 安全考量:防止恶意代码替换核心类导致安全漏洞和系统崩溃
  7. 实际应用:普通应用类可以通过自定义类加载器实现热部署和动态加载