类加载器与双亲委派模型


类加载机制

java虚拟机将class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

类加载器

类加载器作用在类加载阶段。类加载阶段中实现“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作的代码模块成为“类加载器”。
类加载器分为4类:

  • 启动类加载器(Bootstrap ClassLoader): 这个类加载器主要用于加载 JAVA_HOME/lib 目录中的,或者被-Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录下也不会重载)。由C++实现。
  • 扩展类加载器(Extension ClassLoader): 这个类加载器由sun.misc.Launcher$ExtClassLoader实现,加载 JAVA_HOME/lib/ext 目录下的,或者被java.ext.dirs 系统变量所指定的路径中的所有类库。由java实现。
  • 应用程序类加载器(Application ClassLoader): 这个类加载器由sun.misc.Launcher$AppClassLoader实现。同时这个类加载器是ClassLoader 种的getSystemClassLoader方法的返回值,所以也称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库。开发者可以直接使用这个类加载器,如果应用中没有定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。由java实现。
  • 自定义类加载器:用户自定义的类加载器,用于加载特定目录下的类文件。由java实现。

其中除了启动类加载器(Bootstrap ClassLoader)外开发者都可以使用。

他们之间的示意图:

双亲委派模型

启动类加载器(Bootstrap ClassLoader)是顶层类加载器,由C++实现。其他类型的加载器都由自己的父类加载器加载。但是他们之间不是继承的关系,而是组合的关系。

类加载器的双亲委派模型在jdk1.2引入,并沿用至今。

双亲委派模型的流程是:当类加载器需要加载一个类时,首先不会自己去尝试加载这个类,而是把这个加载请求委派给父类加载器进行加载,每一次类加载器都是如此,最终都会传到启动类加载器(Bootstrap ClassLoader)中,只有当父类父类加载器反馈自己无法完成类加载时,即父类加载器的搜索范围内没有找到所需的类时,子加载器才会尝试自己加载。

需要双亲委派模型的原因

  • 保证基础类的唯一性

比如用户自定义了一个Object类,放在classpath下,jvm中将会出现不同的Object类,那么Java类型体系中最基础的行为就无法保证。应用程序也将会变得一片混乱。

双亲模型如何实现的

双亲委派模型的实现代码在类java.lang.ClassLoader中的loadClass方法中:

protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
   
    synchronized (getClassLoadingLock(name)) {
   
        // First, 检查类是否已被加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
   
            long t0 = System.nanoTime();
            try {
   
                if (parent != null) {
   
                    c = parent.loadClass(name, false);
                } else {
   
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
   
                // 父类加载器没加载到时抛异常
            }

            if (c == null) {
   
                // 尝试自己加载
                long t1 = System.nanoTime();
                c = findClass(name);

                // 统计数据
                PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
   
            resolveClass(c);
        }
        return c;
    }
}

此方法的流程非常清晰:

  1. 检查类是否已被加载
  2. 如果存在父类加载器,父类加载器尝试加载
  3. 如果父类加载器无法完成加载时,抛异常
  4. 父类加载器无法加载,子类加载器尝试自己加载
  5. 统计数据

如何破坏双亲委派模型

有三种双亲委派模型被破坏的情况:

  1. jdk1.2之前,双亲委派模型没出现之前
  2. SPI机制,SPI机制的类加载器由启动类加载器完成,但具体实现确是要各个独立厂商实现,放在ClassPath下,所有启动类加载器需要逆向调用应用程序类加载器完成具体厂商的实现。例如JNDI,JDBC,JCE,JAXB,JBI等。
  3. 为了实现热插拔,热部署,模块化,意思是添加一个功能或减去一个功能不用重启,只需要把这模块连同类加载器一起换掉就实现了代码的热替换。

Tomcat的类加载器

Tomcat是web容器,其要解决的问题如下:

  • 一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
  • 部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机,这是不合理的。
  • web容器也有自己依赖的类库,不能于应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。
  • web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情,否则要你何用?所以,web容器需要支持 jsp 修改后不用重启。


Tomcat自己实现了几种类加载器,包括:

  • commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
  • catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
  • sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
  • WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;

CommonClassLoader能加载的类都可以被Catalina ClassLoader和SharedClassLoader使用,从而实现了公有类库的共用,而CatalinaClassLoader和Shared ClassLoader自己能加载的类则与对方相互隔离。
WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。

而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能。

Tomcat是怎么破坏双亲委派模型的?
Tomcat 为了实现隔离性,没有遵守双亲委派模型,每个WebappClassLoader加载自己的目录下的class文件,不会传递给父类加载器。

补充:线程上下文类加载器

作用:为了实现父类加载器使用子类加载器加载指定的类。

线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader方法进行设置。如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过多的话,那这个类加载器默认即使应用程序类加载器。

相关推荐

  1. 双亲委派模型

    2024-02-02 09:08:01       55 阅读
  2. 简单聊聊双亲委派模型

    2024-02-02 09:08:01       37 阅读
  3. 深入理解JVM双亲委派模型机制

    2024-02-02 09:08:01       28 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-02-02 09:08:01       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-02-02 09:08:01       100 阅读
  3. 在Django里面运行非项目文件

    2024-02-02 09:08:01       82 阅读
  4. Python语言-面向对象

    2024-02-02 09:08:01       91 阅读

热门阅读

  1. 关于字符串的常用方法

    2024-02-02 09:08:01       46 阅读
  2. Docker进阶篇-Docker微服务实战

    2024-02-02 09:08:01       44 阅读
  3. Git提交忽略指定文件

    2024-02-02 09:08:01       47 阅读
  4. 【Git】多个托管平台Git账户配置

    2024-02-02 09:08:01       51 阅读
  5. C++ 多线程

    2024-02-02 09:08:01       40 阅读
  6. Django 中的 connection.cursor() 执行 SQL 用法

    2024-02-02 09:08:01       38 阅读
  7. git 删除gitlab上指定的文件夹或者文件

    2024-02-02 09:08:01       46 阅读
  8. [Git版本控制系统]

    2024-02-02 09:08:01       50 阅读