引言
在Java虚拟机(JVM)中,类加载是一个重要的概念,而双亲委派机制是类加载的核心之一。本文将深入研究双亲委派机制,了解它是如何影响Java类加载的。
概述
类加载是Java虚拟机的一种机制,用于将类的二进制数据读入内存并解析成Class对象。每个Java类都必须由某个类加载器加载到内存中。Java类加载器是通过双亲委派模型实现的,这种模型可以保证同一个类只会被同一个类加载器加载。
JVM双亲委派机制是Java类加载器的一种工作机制,它的核心思想是:当一个类加载器收到类加载请求时,首先将这个请求委托给父类加载器去完成,依次递归下去,如果父类加载器可以完成该加载任务,就成功返回;只有父类加载器无法完成此加载请求时(找不到此类),子加载器才会尝试自己去加载。这种机制使得Java的核心API能确保在各种环境和应用中保持稳定。
流程
当一个类加载器收到类加载请求时,会先判断该请求是否合法,如果不合法则直接拒绝;否则,会先将该请求委托给其父类加载器去完成。如果父类加载器无法完成该加载任务,则子类加载器才会尝试自己去加载。
具体来说,双亲委派流程如下:
1)当一个类加载器接收到类加载请求时,首先检查该请求是否合法。
2)如果该请求不合法,则直接拒绝。
3)如果该请求合法,则判断该请求是否需要使用引导类加载器。如果需要使用引导类加载器,则将该请求委托给引导类加载器去完成。
4)如果不需要使用引导类加载器,则将该请求委托给父类加载器去完成。如果父类加载器无法完成该加载任务,则子类加载器才会尝试自己去加载。
5)如果父类加载器可以完成该加载任务,则成功返回。
6)如果所有的父类加载器都无法完成该加载任务,则子类加载器会尝试自己去加载。
图示如下:
工作原理:
-
层次结构:
- Java类加载器被组织成一个层次结构,通常包括三个层次:启动类加载器(Bootstrap Class Loader)、扩展类加载器(Extension Class Loader)和应用程序类加载器(Application Class Loader)。这些加载器之间存在父子关系,构成了一颗树状结构。
-
加载类请求:
-
当程序需要加载一个类时,当前类加载器首先会检查自己是否已经加载过这个类。如果已加载,它会返回该类的引用。
-
如果当前类加载器没有加载过这个类,它会将加载请求委派给其父加载器。
-
-
委派给父加载器:
-
父加载器接收到委派请求后,会依照同样的方式检查是否已加载该类。如果已加载,它会返回该类的引用。
-
如果父加载器也没有加载过该类,它会继续将加载请求委派给其自己的父加载器。这个过程会一直持续,直到达到启动类加载器为止。
-
-
启动类加载器:
-
启动类加载器是位于类加载器层次结构的根部,它通常由JVM的实现提供,负责加载Java核心类库(如
java.lang
包中的类)。 -
如果启动类加载器也无法加载该类,它会抛出
ClassNotFoundException
,指示类找不到。
-
-
加载成功或失败:
-
如果某个父加载器成功加载了类,它将返回该类的引用给子加载器,并加载过程结束。
-
如果所有父加载器都无法加载该类,当前类加载器会尝试自己加载类。如果加载成功,它会将类引用返回给请求者,加载过程结束。
-
如果加载失败,当前类加载器会抛出
ClassNotFoundException
,指示类找不到。
-
通过这种双亲委派模型,类加载器可以确保类的唯一性和安全性。核心类库由启动类加载器加载,避免了用户代码替换核心类的风险。同时,类加载器还可以自定义,以满足应用程序的需求,这有助于实现插件化和动态加载类的功能。双亲委派模型是Java类加载机制的一个关键概念,有助于保障Java程序的稳定性和安全性。
优势
-
类的唯一性和一致性(Class Uniqueness and Consistency):
-
双亲委派模型确保了类的唯一性,因为一个类只会被加载一次。如果一个类已经被加载,即使在不同的类加载器命名空间中也不会再次加载,避免了类的多重定义问题。
-
这有助于维持类加载的一致性,确保在不同的类加载器层次结构中使用的类是同一个版本,避免了类冲突。
-
-
安全性(Security):
-
双亲委派模型有助于提高Java应用程序的安全性。核心类库由启动类加载器加载,这些类库的完整性受到更严格的控制。
-
用户自定义类不容易替代核心类库,从而防止潜在的恶意代码注入或不安全的类加载。
-
-
避免重复加载(Avoidance of Redundant Loading):
- 双亲委派模型避免了重复加载相同的类。当一个类被加载后,它会被缓存,之后的加载请求会直接返回缓存的类引用,提高了性能并节省了内存。
-
层次结构(Hierarchy):
- 双亲委派模型建立了一个层次结构,包括启动类加载器、扩展类加载器和应用程序类加载器。这种层次结构使类加载器的管理更加清晰,有助于隔离不同的类。
-
动态加载和模块化(Dynamic Loading and Modularity):
-
双亲委派模型允许开发人员创建自定义类加载器,这有助于实现动态加载类和模块化的应用程序设计。
-
插件系统和动态模块化系统可以受益于这种灵活性,可以在运行时加载新的类和模块,而不需要重新启动应用程序。
-
自定义类加载器
绕过Java的双亲委派机制通常不是一个推荐的做法,因为双亲委派机制是为了确保类加载的安全性和一致性而设计的。然而,在某些情况下,您可能需要绕过双亲委派机制,例如在实现自定义类加载器时,或者在特殊需求下执行一些高级操作。以下是一些可实现的方法:
- 自定义类加载器
java
// 自定义类加载器
class MyClassLoader extends ClassLoader {
// 构造函数,需要指定父加载器
public MyClassLoader(ClassLoader parent) {
super(parent);
}
// 重写类加载方法
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 在加载类之前可以进行一些自定义的操作
System.out.println("Loading class: " + name);
// 尝试使用父加载器加载类
try {
return super.loadClass(name);
} catch (ClassNotFoundException e) {
// 如果父加载器无法加载,自己尝试加载类
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
// 使用defineClass方法定义类
return defineClass(name, classData, 0, classData.length);
}
}
}
// 模拟从文件或其他来源加载类字节码数据
private byte[] loadClassData(String className) {
// 此处可以根据类名加载对应的字节码数据
// 这里简化为返回一个空的字节数组
return null;
}
}
public class ClassLoadingExample {
public static void main(String[] args) {
// 创建一个自定义类加载器,以系统类加载器作为父加载器
MyClassLoader myClassLoader = new MyClassLoader(ClassLoader.getSystemClassLoader());
try {
// 使用自定义加载器加载一个类
Class<?> customClass = myClassLoader.loadClass("com.example.CustomClass");
// 创建类的实例并调用方法
Object instance = customClass.newInstance();
customClass.getMethod("hello").invoke(instance);
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
在上述示例中,创建了一个自定义的类加载器MyClassLoader
,它尝试加载名为"com.example.CustomClass"的类。如果父加载器无法加载,它会从模拟的数据源加载类字节码数据,并使用defineClass
方法定义类。
-
使用
Thread.currentThread().setContextClassLoader()
:- Java提供了
Thread
类的setContextClassLoader
方法,可以设置线程上下文类加载器。这个类加载器将被用于在当前线程中加载类,而不受双亲委派机制的限制。
javaClassLoader customClassLoader = new MyClassLoader(); Thread.currentThread().setContextClassLoader(customClassLoader); // 在这个线程中加载的类将使用自定义类加载器
- Java提供了
-
使用
Class.forName()
的第二个参数:Class.forName()
方法允许您通过指定类加载器来加载类,可以绕过双亲委派机制。
javaClassLoader customClassLoader = new MyClassLoader(); Class<?> myClass = Class.forName("com.example.MyClass", true, customClassLoader);
注意:
绕过双亲委派机制可能会引入类加载的不一致性和安全性问题,因此应慎重使用。通常情况下,最好遵循双亲委派模型,只在确实需要时才考虑绕过它,同时要确保谨慎处理自定义加载器和类加载操作,以防止潜在的问题。
实际应用
双亲委派机制在实际Java开发中有多种应用。其中一些常见的应用包括:
-
安全性和防止类冲突:通过双亲委派机制,Java确保核心类库只由启动类加载器加载,从而提高了系统的安全性。这样可以防止用户自定义的类意外地替代核心类库。
-
模块化和插件系统:双亲委派模型使得创建模块化和插件化系统更容易。每个模块或插件可以有自己的类加载器,以避免类名冲突,同时还能够访问系统类和其他模块的类。
-
动态加载和热部署:双亲委派模型使得动态加载和热部署变得更加可行。自定义类加载器可以加载新的类或更新现有的类,而不必重新启动整个应用程序。
假设有一个简单的插件系统,每个插件都有自己的类加载器,并且可以动态加载和卸载插件:
java
// 自定义插件类加载器
class PluginClassLoader extends ClassLoader {
public PluginClassLoader(ClassLoader parent) {
super(parent);
}
public Class<?> loadPluginClass(String className) throws ClassNotFoundException {
return super.loadClass(className);
}
}
// 插件接口
interface Plugin {
void run();
}
// 插件示例
class SamplePlugin implements Plugin {
@Override
public void run() {
System.out.println("SamplePlugin is running.");
}
}
public class PluginSystem {
public static void main(String[] args) {
// 创建一个应用程序类加载器
ClassLoader appClassLoader = PluginSystem.class.getClassLoader();
// 创建插件类加载器
PluginClassLoader pluginClassLoader = new PluginClassLoader(appClassLoader);
try {
// 使用插件类加载器加载插件类
Class<?> pluginClass = pluginClassLoader.loadPluginClass("SamplePlugin");
// 创建插件实例并运行
Plugin plugin = (Plugin) pluginClass.newInstance();
plugin.run();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
}
}
在这个示例中,自定义了插件类加载器PluginClassLoader
,它继承自应用程序类加载器,并可以加载插件类。这允许在不破坏应用程序类加载器的情况下加载插件。这是一个简单的示例,实际的插件系统会更加复杂,但它演示了双亲委派模型的应用,可以动态加载并运行插件代码。
参考文献: