一、引言
在Java应用程序启动时,JVM需要将.class文件中的字节码加载到内存中,并对这些类进行验证、准备、解析和初始化。这个过程由一系列称为“类加载器”的组件来完成。每个Java应用程序至少有一个类加载器,它负责加载应用程序所需的类。而类加载器之间通过一种叫做“双亲委派模式”(Parent Delegation Model)的工作机制协同工作。
二、类加载运行全过程
当用java
命令运行某个类的main
函数启动程序时,首先需要通过类加载器把主类加载到JVM。类加载的过程可以概括为以下几步:加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载。在这个过程中,类加载器扮演了至关重要的角色,它们确保了类的正确性和安全性。
- 加载:从硬盘上查找并通过IO读入字节码文件。使用到类时才会加载,例如调用类的
main()
方法或创建对象。 - 验证:校验字节码文件的正确性,以确保其不会破坏JVM的安全性。
- 准备:给类的静态变量分配内存,并赋予默认值。
- 解析:将符号引用替换为直接引用,这是所谓的静态链接过程的一部分。
- 初始化:对类的静态变量初始化为指定的值,执行静态代码块。
- 使用:程序开始正常使用类。
- 卸载:当类不再被需要时,可以从方法区卸载。
三、类加载器
JVM中有几种预定义的类加载器:
- 引导类加载器(Bootstrap ClassLoader):负责加载支撑JVM运行的核心类库,如
rt.jar
等。 - 扩展类加载器(Extension ClassLoader):负责加载位于
$JAVA_HOME/jre/lib/ext
目录下的扩展类包。 - 应用程序类加载器(Application ClassLoader):负责加载ClassPath路径下的类包,主要是开发者编写的类。
- 自定义加载器:用于加载用户自定义路径下的类包,通常由开发者实现。
此外,还存在一个特殊的类加载器实例——sun.misc.Launcher
,它在JVM启动时创建,包含两个主要的类加载器:ExtClassLoader
和AppClassLoader
。其中,AppClassLoader
是大多数情况下用来加载我们自己编写的应用程序的类加载器。
四、双亲委派机制
双亲委派机制是JVM中类加载器的一个重要特性。根据这一机制,当一个类加载器收到类加载请求时,它首先会委托给它的父类加载器去处理;只有当父类加载器无法找到或加载该类时,子类加载器才会尝试自己加载。这种机制的主要优点包括:
- 沙箱安全机制:防止用户定义的类伪装成核心API库的一部分。
- 避免类的重复加载:保证被加载类的唯一性,提高效率并减少资源消耗。
- 全盘负责委托机制:确保一个类及其依赖的所有类都由同一个类加载器加载,维护类之间的关系一致性。
具体来说,当应用程序尝试加载某个类时,首先会交给启动类加载器去查找;如果找不到,则交给扩展类加载器;如果还是找不到,最后才由应用程序类加载器来加载。如果所有类加载器都未能找到对应的类,则会抛出ClassNotFoundException
异常。
五、打破双亲委派机制
虽然双亲委派机制提供了很好的安全性和一致性保障,但在某些特殊情况下可能需要打破这一机制。例如,在Tomcat这样的Web容器中,为了支持不同Web应用间的类隔离以及共享相同版本的第三方库,Tomcat采用了自定义的类加载器结构,打破了传统的双亲委派模式,实现了更灵活的类加载策略。每个Web应用程序都有自己独立的类加载器(WebappClassLoader
),并且JSP文件的热部署也是通过这种方式实现的。
六、自定义类加载器示例
对于想要实现特定功能的场景,比如从网络或其他非标准位置加载类,可以通过继承java.lang.ClassLoader
类并重写findClass
方法来自定义类加载器。下面是一个简单的例子,展示了如何创建一个自定义类加载器来加载特定路径下的.class
文件:
public class MyClassLoader extends ClassLoader {private String classPath;public MyClassLoader(String classPath) {this.classPath = classPath;}protected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] data = loadByte(name);return defineClass(name, data, 0, data.length);} catch (Exception e) {throw new ClassNotFoundException();}}private byte[] loadByte(String name) throws Exception {name = name.replaceAll("\\.", "/");FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");int len = fis.available();byte[] data = new byte[len];fis.read(data);fis.close();return data;}
}
此自定义类加载器可以加载指定路径下的类文件,并且可以通过调整逻辑来适应不同的需求。
七、总结
JVM的类加载器及其双亲委派机制是Java语言的一项关键技术特性,它不仅保证了Java程序的安全性和稳定性,也为开发人员提供了极大的灵活性。理解这些概念对于深入掌握Java编程、解决复杂问题以及进行性能优化等方面都有着重要意义。随着Java技术的发展,类加载机制也在不断演进和完善,以适应日益复杂的软件开发需求。通过深入了解类加载器的工作原理,我们可以更好地设计和构建健壮的Java应用程序。