没有双亲委派机制情况
如果不使用双亲委派机制会加载多次吗?
是的,如果不使用双亲委派机制,同一个类很可能会被多次加载,这会导致严重的问题。
1. 没有双亲委派机制的情况
直接加载类的问题
如果每个类加载器都直接加载自己需要的类,而不进行委托:
// 假设这是一个没有双亲委派机制的类加载器
public class SimpleClassLoader extends ClassLoader {
private String classPath;
public SimpleClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 不进行双亲委派,直接自己加载
Class<?> c = findClass(name); // 直接加载,不检查是否已加载
if (resolve) {
resolveClass(c);
}
return c;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] loadClassData(String className) {
// 加载类数据的实现...
return null;
}
}
多次加载的后果
public class NoDelegationExample {
public static void main(String[] args) throws Exception {
// 使用没有双亲委派机制的类加载器
SimpleClassLoader loader1 = new SimpleClassLoader("path1");
SimpleClassLoader loader2 = new SimpleClassLoader("path2");
// 同一个类被不同加载器多次加载
Class<?> class1 = loader1.loadClass("com.example.MyClass");
Class<?> class2 = loader2.loadClass("com.example.MyClass");
// 结果是不同的类
System.out.println("class1 == class2: " + (class1 == class2)); // false
System.out.println("class1.equals(class2): " + class1.equals(class2)); // false
// 会导致类型转换异常
Object obj1 = class1.newInstance();
// 下面这行会抛出ClassCastException
// class2.cast(obj1); // 异常:不能转换
}
}
2. 实际问题演示
类型不兼容问题
// 模拟没有双亲委派机制的情况
public class TypeCompatibilityIssue {
public static void main(String[] args) throws Exception {
// 两个不同的类加载器加载同一个类
CustomClassLoader loader1 = new CustomClassLoader("classes");
CustomClassLoader loader2 = new CustomClassLoader("classes");
// 加载同一个类
Class<?> string1 = loader1.loadClass("java.lang.String");
Class<?> string2 = loader2.loadClass("java.lang.String");
// 创建实例
String str1 = (String) string1.getConstructor(String.class)
.newInstance("Hello");
Object str2 = string2.getConstructor(String.class)
.newInstance("World");
// 问题出现:即使都是String类,但由于不同加载器加载,无法直接赋值
// List<string1> 和 List<string2> 是不同的类型
try {
// 这会抛出异常,因为类型不匹配
boolean result = str1.equals(str2);
} catch (Exception e) {
System.out.println("类型不兼容: " + e.getMessage());
}
}
}
内存浪费问题
public class MemoryWasteExample {
public static void demonstrateWaste() throws Exception {
List<Class<?>> loadedClasses = new ArrayList<>();
// 模拟多次加载同一个类
for (int i = 0; i < 100; i++) {
CustomClassLoader loader = new CustomClassLoader("classes");
Class<?> clazz = loader.loadClass("com.example.HeavyClass");
loadedClasses.add(clazz);
}
// 每个类加载器都创建了独立的类元数据
// 造成内存中存在100个相同的类定义
System.out.println("Loaded " + loadedClasses.size() + " class instances");
// 验证它们不是同一个类
for (int i = 0; i < loadedClasses.size() - 1; i++) {
System.out.println("Class " + i + " == Class " + (i+1) + ": " +
(loadedClasses.get(i) == loadedClasses.get(i+1)));
}
}
}
3. 安全性问题
核心类库被篡改
// 如果没有双亲委派,用户可以加载恶意的系统类
public class SecurityRiskExample {
public static void main(String[] args) throws Exception {
// 假设用户在自己的classpath下放置了一个恶意的String类
MaliciousClassLoader loader = new MaliciousClassLoader("user/classes");
// 没有双亲委派的情况下,可能会加载用户自定义的String类
Class<?> fakeString = loader.loadClass("java.lang.String");
// 这个"String"类可能包含恶意代码
Object fakeStringInstance = fakeString.newInstance();
// 当程序使用这个"String"时,可能会执行恶意代码
System.out.println("危险:加载了可能被篡改的核心类");
}
}
4. 正常双亲委派机制的对比
正确的类加载行为
public class ProperDelegationExample {
public static void main(String[] args) throws Exception {
// 使用标准的双亲委派机制
ClassLoader loader1 = new CustomClassLoader("classes");
ClassLoader loader2 = new CustomClassLoader("classes");
// 都会委托给Bootstrap ClassLoader加载核心类
Class<?> string1 = loader1.loadClass("java.lang.String");
Class<?> string2 = loader2.loadClass("java.lang.String");
// 结果是同一个类
System.out.println("string1 == string2: " + (string1 == string2)); // true
// 类型兼容,不会出现转换异常
String str1 = (String) string1.getConstructor(String.class)
.newInstance("Hello");
String str2 = (String) string2.getConstructor(String.class)
.newInstance("World");
// 可以正常比较和操作
boolean result = str1.equals(str2); // 正常工作
System.out.println("比较结果: " + result);
}
}
5. 实际场景中的问题
Web应用服务器中的类加载
// 在应用服务器中,如果没有正确的类加载机制
public class WebAppProblem {
public static void demonstrateIssue() throws Exception {
// 每个Web应用有自己的类加载器
WebAppClassLoader app1Loader = new WebAppClassLoader("app1/WEB-INF/classes");
WebAppClassLoader app2Loader = new WebAppClassLoader("app2/WEB-INF/classes");
// 如果不使用双亲委派
Class<?> servlet1 = app1Loader.loadClass("javax.servlet.http.HttpServlet");
Class<?> servlet2 = app2Loader.loadClass("javax.servlet.http.HttpServlet");
// 问题:
// 1. 浪费内存:两个相同的类被加载两次
// 2. 类型不兼容:app1的Servlet和app2的Servlet被视为不同类型
// 3. 安全风险:可能加载了被篡改的Servlet API
System.out.println("Servlet类相同: " + (servlet1 == servlet2)); // false (问题!)
}
}
总结
如果不使用双亲委派机制:
- 重复加载:同一个类可能被多个类加载器重复加载
- 类型不兼容:即使是相同的类,不同加载器加载的实例也无法互相转换
- 内存浪费:JVM中存在多个相同的类定义,浪费内存
- 安全风险:核心类库可能被恶意替换
- 程序异常:出现
ClassCastException
等类型转换异常
双亲委派机制正是为了解决这些问题而设计的,它确保了类的唯一性和安全性,是Java平台稳定运行的重要保障。