Tomcat 的类加载机制

Tomcat 的类加载机制Tomcat实际上只有WebAppClassLoader加载器中打破了双亲委派,其他类加载器还是遵循双亲委派的。这样做最主要原因是保证同个Web容器中的不同Web应用程序所使用的类库相互独立,避免相互影响

大家好,又见面了,我是你们的朋友全栈君。

        在前面 Java虚拟机:对象创建过程与类加载机制、双亲委派模型 文章中,我们介绍了 JVM 的类加载机制以及双亲委派模型,双亲委派模型的类加载过程主要分为以下几个步骤:

  • (1)初始化 ClassLoader 时需要指定自己的 parent 是谁
  • (2)先检查类是否已经被加载过,如果类已经被加载了,直接返回
  • (3)若没有加载则调用父加载器 parent 的 loadClass() 方法进行加载
  • (4)若父加载器为空则默认使用启动类加载器 bootstrap ClassLoader 进行加载
  • (5)如果父类加载失败,抛出 ClassNotFoundException 异常后,再调用自己的 findClass() 方法进行加载。

        前面文章也提到,如果想要破坏这种机制,那么就自定义一个类加载器(继承自 ClassLoader),并重写其中的 loadClass() 方法,使其不进行双亲委派即可。最经典例子就是 Tomcat 容器的类加载机制了,它实现了自己的类加载器 WebApp ClassLoader,并且打破了双亲委派模型,在每个应用在部署后,都会创建一个唯一的类加载器。

1、Tomcat 的类加载器结构图:

Tomcat 的类加载机制

(1)Common ClassLoader:加载 common.loader 属性下的 jar,一般是 CATALINA_HOME/lib 目录下,主要是 tomcat 使用以及应用通用的一些类

(2)Catalina ClassLoader:加载 server.loader 属性下的 jar,默认未配置路径,返回其父加载器即 Common ClassLoader,主要是加载服务器内部可⻅类,这些类应⽤程序不能访问;

(3)Shared Classloader:加载 share.loader 属性下的jar,默认未配置路径,返回其父加载器即 Common ClassLoader,主要是加载应⽤程序共享类,这些类对 Tomcat 自己不可见;

只有指定了 tomcat/conf/catalina.properties 配置文件的 server.loader 和 share.loader 项后,才会真正建立 Catalina ClassLoader 和 Shared ClassLoader 的实例,否则在用到这两个类加载器的地方都会用 Common ClassLoader 的实例代替,而默认的配置文件中是没有设置这两个 loader 项的

(4)WebApp ClassLoader:Tomcat 可以存在多个 WebApp ClassLoader 实例,每个应⽤程序都会有⼀个独⼀⽆⼆的 WebApp ClassLoader,⽤来加载本应⽤程序 /WEB-INF/classes 和 /WEB-INF/lib 下的类。

2、Tomcat 的类加载流程说明:

当 Tomcat 使用 WebAppClassLoader 进行类加载时,具体过程如下:

(1)先在本地 cache 缓存中查找该类是否已经加载过,看看 Tomcat 有没有加载过这个类

(2)如果 Tomcat 没有加载过这个类,则从系统类加载器的 cache 缓存中查找是否加载过

(3)如果没有,则使用 ExtClassLoader 类加载器类加载,重点来了,Tomcat 的 WebAppClassLoader 并没有先使用 AppClassLoader 来加载类,而是直接使用了 ExtClassLoader 来加载类。不过 ExtClassLoader 依然遵循双亲委派,它会使用 Bootstrap ClassLoader 来对类进行加载,保证了 Jre 里面的核心类不会被重复加载。

比如在 Web 中加载一个 Object 类。WebAppClassLoader → ExtClassLoader → Bootstrap ClassLoader,这个加载链,就保证了 Object 不会被重复加载。

(4)如果没有加载成功,WebAppClassLoader 就会调用自己的 findClass() 方法由自己来对类进行加载,先在 WEB-INF/classes 中加载,再从 WEB-INF/lib 中加载。

(5)如果仍然未加载成功,WebAppclassLoader 会委派给 SharedClassLoader,SharedClassLoad 再委派给 CommonClassLoader,CommonClassLoader 委派给 AppClassLoader,直到最终委派给 BootstrapClassLoader,最后再一层一层地在自己目录下对类进行加载。

(6)都没有加载成功的话,抛出异常。

3、源码解析:

(1)WebAppClassLoader 的 loadClass() 方法源码:

WebappClassLoader 应用类加载器的 loadClass 在他的父类 WebappClassLoaderBase 中

public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        Class<?> clazz = null;
        //1. 先在本地cache查找该类是否已经加载过
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }
        //2. 从系统类加载器的cache中查找是否加载过
        clazz = findLoadedClass(name);
        if (clazz != null) {
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }
        // 3. 尝试用ExtClassLoader类加载器类加载(ExtClassLoader 遵守双亲委派,ExtClassLoader 会使用 Bootstrap ClassLoader 对类进行加载)
        ClassLoader javaseLoader = getJavaseClassLoader();
        try {
            clazz = javaseLoader.loadClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }
        // 4. 尝试在本地目录搜索class并加载
        try {
            clazz = findClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }
        // 5. 尝试用系统类加载器(AppClassLoader)来加载
        try {
            clazz = Class.forName(name, false, parent);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }
     }
    //6. 上述过程都加载失败,抛出异常
    throw new ClassNotFoundException(name);
}

(2)WebAppClassLoader 的 findClass() 方法源码:

public Class<?> findClass(String name) throws ClassNotFoundException {
    // Ask our superclass to locate this class, if possible
    // (throws ClassNotFoundException if it is not found)
    Class<?> clazz = null;

    // 先在自己的 Web 应用目录下查找 class
    clazz = findClassInternal(name);

    // 找不到 在交由父类来处理
    if ((clazz == null) && hasExternalRepositories) {  
        clazz = super.findClass(name);
    }
    if (clazz == null) {
         throw new ClassNotFoundException(name);
    }
    return clazz;
}

4、为什么tomcat要实现自己的类加载机制:

        WebAppClassLoader 加载类的时候,故意打破了JVM 双亲委派机制,绕开了 AppClassLoader,直接先使用 ExtClassLoader 来加载类。最主要原因是保证部署在同一个 Web 容器上的不同 Web 应用程序所使用的类库可以实现相互隔离,避免不同项目的相互影响。当然还有其他原因,如:

(1)保证 Web 容器自身的安全不受部署的 Web 应用程序影响,所以 Tomcat 使用的类库要与部署的应用的类库相互独立

(2)保证部分基础类不会被同时加载,有些类库 Tomcat 与部署的应用可以共享,比如说 servlet-api

(3)保证部署在同一个 Web 容器的应用之间的类库可以共享,这听起来好像主要原因相互矛盾,但其实这很合理,类被类加载器加载到虚拟机后,会存放在方法区的永久代中,如果类库不能共享,虚拟机的方法区就会很容易出现过度膨胀的风险。比如这时候如果有大量的应用使用 spring 来管理,如果 spring 类库不能共享,那每个应用的 spring 类库都会被加载一次,将会是很大的资源浪费。

小结:Tomcat 实际上只有 WebAppClassLoader 加载器中打破了双亲委派,其他类加载器还是遵循双亲委派的。 这样做最主要原因是保证同个 Web 容器中的不同 Web 应用程序所使用的类库相互独立,避免相互影响

参考文章:https://mp.weixin.qq.com/s/OwWUDxHY4Th6decmJeMTgA

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/142532.html原文链接:https://javaforall.net

(0)
上一篇 2022年5月23日 下午3:20
下一篇 2022年5月23日 下午3:20


相关推荐

  • 在html中加入外部css样式,如何引入CSS样式表?

    在html中加入外部css样式,如何引入CSS样式表?CSS用于修饰网页样式,但是,如果希望CSS修饰的样式起作用,就必须在html档中引入CSS样式表。引入样式表的常用方式有三种,即行内式、内嵌式、外链式,具体介绍如下。1.行内式行内式也称内联样式,是通过标记的Istyle属性来设置标记的样式,其基本语法格式如下:内容标记名>上述语法中,style是标记的属性,实际上任何HTML标记都拥有style属性,用来设置行内式。属性和属性值的书写…

    2022年7月14日
    23
  • 实战:在 Docker (Windows) 中构建集成 yt-dlp 的“满血版” n8n 自动化工作流

    实战:在 Docker (Windows) 中构建集成 yt-dlp 的“满血版” n8n 自动化工作流

    2026年3月15日
    2
  • 模电学习总结

    模电学习总结放大电路的主要性能指标➢电压放大倍数(电压增益)➢输入电阻.➢输出电阻➢通频带➢非线性失真系数、最大输出不失真电压、最大输出功率与效率.Au=uoui=U˙oU˙IA_{u}=\frac{u_{o}}{u_{i}}=\frac{\dot{U}_{o}}{\dot{U}_{I}}Au​=ui​uo​​=U˙I​U˙o​​Ri=uiiiR_{i}=\frac{u_{i}}{i_{…

    2022年6月20日
    33
  • 读懂蛋白质PDB文件

    读懂蛋白质PDB文件对于从事生物行业的朋友们来说,PDB文件和蛋白质结构是很多人绕不过去的问题。然而对于天天跑电泳过柱子的生物狗来说,PDB文件打开后与天书无异。这里,我转载一篇网上看到的关于PDB文件内记号说明的文章,希望对大家有用!教你读懂蛋白质的PDB文件&n…

    2022年6月2日
    159
  • Java输入输出(标准)

    Java输入输出(标准)简述Java输入就需要自己构造类了。其中nextLine()这个方法,表示的是,一直读,读到有换行符之后就截至(不取换行符)代码importjava.util.*;publicclassTest{publicstaticvoidmain(String[]args){Scannerin=newScanner(System….

    2022年5月15日
    40
  • 论文写作利器—LaTeX教程(入门篇)(更新中)

    论文写作利器—LaTeX教程(入门篇)(更新中)一、LaTeX简介结合维基百科及LaTeX官网可知:LaTeX(/ˈlɑːtɛx/,常被读作/ˈlɑːtɛk/或/ˈleɪtɛk/)是一种基于TeX的高品质排版系统,由美国计算机科学家莱斯利·兰伯特在20世纪80年代初期开发,非常适用于生成高印刷质量的科技和数学、物理文档,尤其擅长于复杂表格和数学公式的排版。LaTeX是科学文献交流和出版的事实标准。简单来说,相比于Word排版时需要设…

    2022年7月14日
    44

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号