Java的类加载机制是Java虚拟机(JVM)的一个重要特性,确保了Java应用程序能够运行在不同的环境下。类加载机制主要负责以下三个主要任务:
加载:查找并加载类的二进制数据。 连接:
验证:确保加载的类的正确性。 准备:为类变量分配内存,并设置类变量的默认初始值。 解析:将类中的符号引用转换为直接引用。
类加载机制涉及以下几个关键概念:
类加载器(ClassLoader):JVM通过类加载器来加载类。在Java中,类加载器是一个能够将类加载到JVM中的对象,它主要分为以下四种:
引导(Bootstrap)类加载器:这是虚拟机自带的类加载器,负责加载Java的核心库(如rt.jar中的类),无法直接获取。 扩展(Extension)类加载器:这是加载Java平台扩展功能的类加载器,它负责加载 $JAVA_HOME/lib/ext
目录中或由系统属性java.ext.dirs
指定位置中的类库。应用程序(Application)类加载器:这是类 ClassLoader
的系统默认实现,负责加载应用程序classpath上的类库。用户自定义(User-Defined)类加载器:开发者可以通过继承 ClassLoader
类来创建自己的类加载器,以实现特定需求。双亲委派模型(Parent Delegation Model):当一个类加载器收到类加载的请求时,它不会首先尝试自己去加载这个类,而是把请求代理给它的父类加载器去执行。如果父加载器不能完成这个加载(例如,因为它不认识这个类),子加载器才会尝试自己去加载这个类。
这种模型的优势在于Java类总是被某个被信任的类加载器所加载,这保证了Java应用运行时的安全性。同时也避免了类的多次加载,由于在JVM内存中,同一个类可以被加载多次,如果使用不同的类加载器的话,那么这些类虽然来源相同但会被认为是不同的类。
Tomcat 作为一个 Servlet 容器,实现了 Java EE 的 servlet 规范,并提供了自己的类加载器结构,用于加载和管理不同 Web 应用程序的类和资源。Tomcat 的类加载器结构能够实现对每个 Web 应用程序的隔离,这意味着它确实“打破”了标准的 Java 类加载机制中的双亲委派模型,至少是相对于 Java EE Web 应用的上下文环境而言的。
Tomcat的类加载器架构主要由以下几个层次组成:
Bootstrap ClassLoader:最顶层的类加载器,负责加载 JVM 自身的类库,如 rt.jar 包中的类,这是 JVM 的一部分。
System ClassLoader:加载系统类路径(
CLASSPATH
)中的类,通常加载的是你的应用程序的类。Common ClassLoader:加载Tomcat的
lib
目录下的 JAR 文件和类。它们对所有的 Web 应用程序都是可见的。Catalina ClassLoader(可选):负责加载Tomcat服务器自身的类,通常不涉及 Web 应用程序。
Shared ClassLoader(可选):如果配置正确,它可以用来加载多个 Web 应用程序所共享的类和资源,通常是存放在
${catalina.base}/shared
目录下。Webapp ClassLoader:每个 Web 应用都有自己的类加载器实例,它加载位于
/WEB-INF/classes
和/WEB-INF/lib
下的类和 JAR 文件。这些类对于其他 Web 应用不可见,确保了 Web 应用之间的隔离性。
正是由于 Tomcat 提供了 Webapp ClassLoader,它允许每个应用使用自己的类路径,并存放自己的库文件,使得相同的 Java 类可以存在于不同的 Web 应用中,而互不干扰。这一点区别于传统 Java 应用,其中的类加载器通常遵循双亲委派模型,最终会沿着类加载器的层级树向上查找,直至 Bootstrap ClassLoader。
Tomcat 打破双亲委派模型的关键在于 Webapp ClassLoader 的行为。在标凈的双亲委派模型中,类加载器首先会委派到父加载器去尝试加载一个类,只有在父加载器加载失败时才会尝试自己加载。但是为了实现 Web 应用的隔离,Tomcat 的 Webapp ClassLoader 在尝试委派给父加载器之前,首先会尝试自己加载这个类。这种自下而上的加载行为违反了标准的双亲委派模型。
这种机制有助于防止应用之间共享类库或者彼此影响,从而在 Tomcat 这样的容器中实现多个应用稳定和安全地并行运行。不过,如果开发者不希望打破双亲委派模型,也可以通过配置使得 Tomcat 中的 Web 应用遵循标准 Java 类加载器的行为。