一文讲透 Tomcat 的类加载机制!揭秘类加载核心


-     前言     -

你了解 Apache Tomcat 的类加载机制吗?本文将从底层原理切入,彻底揭秘 Tomcat 类加载所涉及的源码、机制和方案,助你深入掌握 Tomcat 类加载核心!

在啃这篇干货的同时,【文章结尾处】还有玄姐送你的【免费福利】在等你,记得查收哦!

-     JVM 类加载器     -

1、JVM类加载器

说起 Tomcat 类加载器,就不得不先简单说一下 JVM 类加载器,如下图所示:

  • 启动类加载器:Bootstrap ClassLoader,用于加载JVM提供的基础运行类,即位于%JAVA_HOME%/jre/lib目录下的核 心类库;

  • 扩展类加载器:Extension ClassLoader, Java提供的一个标准的扩展机制用于加载除核心类库外的Jar包,即只要复制 到指定的扩展目录(可以多个)下的Jar, JVM会自动加载(不需要通过-classpath指定)。默认的扩展目录是%JAVA_HOME%加e/lib/ext。典型的应用场景就是,Java使用该类加载 器加载JVM默认提供的但是不属于核心类库的Jar。不推荐将应用程序依赖的 类库放置到扩展目录下,因为该目录下的类库对所有基于该JVM运行的应用程序可见;

  • 应用程序类加载器:Application ClassLoader ,用于加载环境变量CLASSPATH (不推荐使用)指定目录下的或者-classpath运行 参数指定的Jar包。System类加载器通常用于加载应用程序Jar包及其启动入口类(Tomcat 的Bootstrap类即由System类加载器加载)。

这些类加载器的工作原理是一样的,区别是它们的加载路径不同,也就是说 findClass 这个方法查找的路径不同。

双亲委托机制是为了保证一个 Java 类在 JVM 中是唯一的,假如你不小心写了一个与 JRE 核心类同名的类,比如 Object 类,双亲委托机制能保证加载的是 JRE 里的那个 Object 类,而不是你写的 Object 类。

这是因为 AppClassLoader 在加载你的 Object 类时,会委托给 ExtClassLoader 去加载,而 ExtClassLoader 又会委托给 BootstrapClassLoader,BootstrapClassLoader 发现自己已经加载过了 Object 类,会直接返回,不会去加载你写的 Object 类。

这里请注意,类加载器的父子关系不是通过继承来实现的,比如 AppClassLoader 并不是 ExtClassLoader 的子类,而是说 AppClassLoader 的 parent 成员变量指向 ExtClassLoader 对象。同样的道理,如果你要自定义类加载器,不去继承 AppClassLoader,而是继承 ClassLoader 抽象类,再重写 findClass 和 loadClass 方法即可,Tomcat 就是通过自定义类加载器来实现自己的类加载逻辑。不知道你发现没有,如果你要打破双亲委托机制,就需要重写 loadClass 方法,因为 loadClass 的默认实现就是双亲委托机制。

2、类加载器的源码

public abstract class ClassLoader {  //  每个类加载器都有一个父加载器  private final ClassLoader parent;  public Class<?> loadClass(String name) throws ClassNotFoundException {        return loadClass(name, false);    }     protected Class<?> loadClass(String name, boolean resolve)        throws ClassNotFoundException    {            // First, check if the class has already been loaded            Class<?> c = findLoadedClass(name);           // 如果没有加载过            if (c == null) {                if (parent != null) {                  //  先委托给父加载器去加载,注意这是个递归调用                 c = parent.loadClass(name, false);                } else {                 // 如果父加载器为空,查找 Bootstrap 加载器是不是加载过了                   c = findBootstrapClassOrNull(name);                }                          // 如果父加载器没加载成功,调用自己的 findClass 去加载                if (c == null) {                            c = findClass(name);                }            }                     return c;        }            }    //ClassLoader 中findClass方式需要被子类覆盖,下面这段代码就是对应代码      protected Class<?> findClass(String name){       //1. 根据传入的类名 name,到在特定目录下去寻找类文件,把.class 文件读入内存          ...       //2. 调用 defineClass 将字节数组转成 Class 对象       return defineClass(buf, off, len);    }      // 将字节码数组解析成一个 Class 对象,用 native 方法实现    protected final Class<?> defineClass(byte[] b, int off, int len){        }    }

我们自定义类加载器就需要重写ClassLoader的loadClass方法。

-     Tomcat 的类加载机制     -

1、加载机制的特点

  • 隔离性:Web应用类库相互隔离,避免依赖库或者应用包相互影响。设想一下,如果我们 有两个Web应用,一个釆用了Spring 2.5, 一个采用了Spring 4.0,而应用服务器使用一个 类加载器加载,那么Web应用将会由于Jar包覆盖而导致无法启动成功;

  • 灵活性:既然Web应用之间的类加载器相互独立,那么我们就能只针对一个Web应用进行 重新部署,此时该Web应用的类加载器将会重新创建,而且不会影响其他Web应用。如果 釆用一个类加载器,显然无法实现,因为只有一个类加载器的时候,类之间的依赖是杂 乱无章的,无法完整地移除某个Web应用的类;

  • 性能:由于每个Web应用都有一个类加载器,因此Web应用在加载类时,不会搜索其他 Web应用包含的Jar包,性能自然高于应用服务器只有一个类加载器的情况。

2、Tomcat 的类加载方案

  • 引导类加载器 和 扩展类加载器 的作⽤不变;

  • 系统类加载器正常情况下加载的是 CLASSPATH 下的类,但是 Tomcat 的启动脚本并未使⽤该变量,⽽是加载tomcat启动的类,⽐如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。位于CATALINA_HOME/bin下;

  • Common 通⽤类加载器加载Tomcat使⽤以及应⽤通⽤的⼀些类,位于CATALINA_HOME/lib下,⽐如servlet-api.jar;

  • Catalina ClassLoader ⽤于加载服务器内部可⻅类,这些类应⽤程序不能访问;

  • SharedClassLoader ⽤于加载应⽤程序共享类,这些类服务器不会依赖;

  • WebappClassLoader,每个应⽤程序都会有⼀个独⼀⽆⼆的Webapp ClassLoader,他⽤来加载本应⽤程序 /WEB-INF/classes 和 /WEB-INF/lib 下的类。

tomcat 8.5 默认改变了严格的双亲委派机制:

  • 从缓存中加载;

  • 如果缓存中没有,会先调用ExtClassLoader进行加载, 扩展类加载器是遵循双亲委派的,他会调用bootstrap,查看对应的lib有没有,然后回退给ExtClassLoader对扩展包下的数据进行加载;

  • 如果未加载到,则从 /WEB-INF/classes加载;

  • 如果未加载到,则从 /WEB-INF/lib/*.jar 加载如果未加载到,WebAppclassLoader 会委派给SharedClassLoader,SharedClassLoad会委派给CommonClassLoader.....,依次委派给BootstrapClassLoader, 然后BootstrapClassLoader 在自己目录中查找对应的类如果有则进行加载,如果没有他会委派给下一级ExtClassLoader,ExtClassLoader再查找自己目录下的类,如果有则加载如果没有则委派给下一级……遵循双亲委派原则。

3、分析应用类加载器的加载过程

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

  public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {        synchronized (getClassLoadingLock(name)) {            if (log.isDebugEnabled())                log.debug("loadClass(" + name + ", " + resolve + ")");            Class<?> clazz = null;            // Log access to stopped class loader            checkStateForClassLoading(name);                //从当前ClassLoader的本地缓存中加载类,如果找到则返回            clazz = findLoadedClass0(name);            if (clazz != null) {                if (log.isDebugEnabled())                    log.debug("  Returning class from cache");                if (resolve)                    resolveClass(clazz);                return clazz;            }            // 本地缓存没有的情况下,调用ClassLoader的findLoadedClass方法查看jvm是否已经加载过此类,如果已经加载则直接返回。            clazz = findLoadedClass(name);            if (clazz != null) {                if (log.isDebugEnabled())                    log.debug("  Returning class from cache");                if (resolve)                    resolveClass(clazz);                return clazz;            }            String resourceName = binaryNameToPath(name, false);            //此时的javaseClassLoader是扩展类加载器  是把扩展类加载器赋值给了javaseClassLoader            ClassLoader javaseLoader = getJavaseClassLoader();            boolean tryLoadingFromJavaseLoader;            try {              .....            //如果可以用getResource得到            //如果能用扩展类加载器的getResource得到就证明可以被扩展类加载器加载到接下来安排扩展类加载器加载            if (tryLoadingFromJavaseLoader) {                try {                    //使用扩展类加载器进行加载                    clazz = javaseLoader.loadClass(name);                    if (clazz != null) {                        if (resolve)                            resolveClass(clazz);                        return clazz;                    }                } catch (ClassNotFoundException e) {                    // Ignore                }            }            // (0.5) Permission to access this class when using a SecurityManager            if (securityManager != null) {                int i = name.lastIndexOf('.');                if (i >= 0) {                    try {                        securityManager.checkPackageAccess(name.substring(0,i));                    } catch (SecurityException se) {                        String error = "Security Violation, attempt to use " +                            "Restricted Class: " + name;                        log.info(error, se);                        throw new ClassNotFoundException(error, se);                    }                }            }            boolean delegateLoad = delegate || filter(name, true);            // (1) Delegate to our parent if requested            //如果是true就是用父类加载器进行加载            if (delegateLoad) {                if (log.isDebugEnabled())                    log.debug("  Delegating to parent classloader1 " + parent);                try {                    clazz = Class.forName(name, false, parent);                    if (clazz != null) {                        if (log.isDebugEnabled())                            log.debug("  Loading class from parent");                        if (resolve)                            resolveClass(clazz);                        return clazz;                    }                } catch (ClassNotFoundException e) {                    // Ignore                }            }            // (2) Search local repositories            if (log.isDebugEnabled())                log.debug("  Searching local repositories");            try {                // 本地进行加载                clazz = findClass(name);                if (clazz != null) {                    if (log.isDebugEnabled())                        log.debug("  Loading class from local repository");                    if (resolve)                        resolveClass(clazz);                    return clazz;                }            } catch (ClassNotFoundException e) {                // Ignore            }            // (3) Delegate to parent unconditionally            //到这里还是没有加载上再次尝试使用父类加载器进行加载            if (!delegateLoad) {                    if (log.isDebugEnabled())                    log.debug("  Delegating to parent classloader at end: " + parent);                try {                    clazz = Class.forName(name, false, parent);                    if (clazz != null) {                        if (log.isDebugEnabled())                            log.debug("  Loading class from parent");                        if (resolve)                            resolveClass(clazz);                        return clazz;                    }                } catch (ClassNotFoundException e) {                    // Ignore                }            }        }        throw new ClassNotFoundException(name);    }

注:在37行英文注释中标注获取的是系统类加载器,但我们debug的时候会发现他是扩展类加载器,实际中我们可以推断出他应该是扩展类加载器,因为如果我们加载的类在扩展类加载器路径下已经存在的话,那我们直接调用系统类加载器是就是错误的了,下图为debug后获取的类加载器的验证。

总结:tomcat打破了双亲委派的原则,实际是在应用类加载器中打破了双亲委派,其他类加载器还是遵循双亲委派的。

一份来自玄姐的特别福利!

2020 年即将过去,展望“薪”未来,技术人如何在激烈竞争中脱颖而出,在技术的海洋中绽放属于自己的精彩?今晚 20:00,玄姐(前58技术委员会主席孙玄)将与前 360 技术总监等大咖齐聚直播间,聚焦后端技术发展新动向,助你在 2021 年势如破竹,“牛”转乾坤!

事件:奈学教育年末直播专场-粉丝通宵狂欢夜

主题:探索后端技术最新发展动向

嘉宾:前58集团技术委员会主席孙玄、前360技术总监易洋、前华为终端硬件研发专家姚劲、前转转公司大数据负责人李希沅

时间:12月28日,晚20:00 不见不散!

长按识别二维码进入直播间

今晚 20:00 大咖在直播间等你!

直播间好礼送不停,更有“年终宠粉超值回馈”等精彩活动亮相,百余份好礼等你免费领取,一定要来!

-   关注“架构之美”   -

点击“阅读原文”,添加奈学课程顾问老师领取更多免费干货!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: ESD(Electrostatic Discharge,静电放电)是一种瞬时放电现象,通常是由人体或设备上积累的静电电荷引起的。一般来说,ESD会导致电子设备损坏或误操作,因此必须采取措施来避免ESD。 在设计中,ESD保护应该开始于PCB的物理设计。一个好的物理设计将使ESD泄放的能量尽可能地均匀地分散到整个电路板上。这种物理设计包括有效的接地,涂覆和排列PCB层。同时,这也需要考虑到整个系统的电缆结构、机箱接地和隔离等因素,从而最大限度地提高整个系统的耐ESD能力。 此外,在设计电路时,还需要考虑到ESD保护措施。主要的保护措施包括使用可靠的ESD保护器件,如TVS器件、瞬变压抑器和热释电器件,以保护线路免受ESD的影响。此外,在设计输入、输出和供电接口时,还应该采用合适的线路过滤器和电容器,以进一步提高系统的ESD耐受性。 最后,测试是ESD保护设计的重要环节。ESD测试可以验证保护设计的有效性,并排除措施上的缺陷。通常,测试人员会使用标准ESD模拟器来模拟真实的ESD事件。在测试过程中,应注意对设备进行预处理,如去静电和适当的人体模拟。此外,还应该制定合适的检验标准以确保测试的准确性和可重复性。 总之,ESD保护设计至关重要,因为它能够保护电子设备免受静电放电的损害。为了实现可靠的ESD保护,这需要考虑物理设计和电路设计,以及有效的测试工具。最后,只有将所有这些因素合理结合,才能实现有效的ESD保护设计。 ### 回答2: ESD(Electrostatic Discharge,静电放电)指的是在两个带有不同电荷的物体接触或者靠近时,电荷之间发生放电的现象。这种放电可以对各种电子元器件和电路造成损害,从而影响设备的性能和寿命。 ESD的原理可以通过三种方式传递:空气中的放电、直接接触和电感耦合。在实际应用中,ESD对硅芯片、存储器、晶体管等电子元件的损害是非常严重的,这些元件的特性和结构容易受到ESD的影响。 为了防止ESD对电子元件和电路的损坏,需要在设计中采用一些专门的技术,比如在元器件和电路板上增加ESD保护电路、在设备外壳上增加处理工艺等。对于集成电路芯片而言,可以采用对基底和指的进行控制,以及在芯片电路设计过程中合理选择元器件和适当布局等。 总之,ESD保护是电子元器件和电路设计中非常重要的一环,需要采用针对性的技术来减缓和防止ESD对设备的影响,从而保证设备的长期稳定性和可靠性。 ### 回答3: ESD全程为静电放电,是由于静电在两者之间产生的高电压放电引起的电感和电容的相互作用。在现代电子系统中,由于设备的电路越来越小,因此更容易受到静电干扰,人们不得不在设计中考虑如何避免或降低这种静电干扰。本文将从ESD的原理出发,简要介绍如何在电路设计中考虑防止ESD干扰。 ESD的产生是由于静电的积累导致的高电压放电,因此防止ESD干扰的基本原则是减小静电的积累。在电路设计中,静电主要通过两个方面来进行干扰:一是直接放电干扰,即静电直接放电到电路中,导致电路损坏;二是间接放电干扰,即静电放电到设备的金属外壳等部位,导致电磁场干扰影响电路的正常工作。因此,在设计中,需要采用一些措施来减小这些干扰。 1. 选择合适的元器件:在元器件的选择上,要选择一些抗ESD干扰的元器件,如采用ESD保护二极管等,能够减小ESD对电路的影响。 2. 优化电路结构:在电路设计中,要优化电路结构,减少电路间的交叉干扰,避免电路产生高电位差,这样能够减少静电的积累和ESD的辐射。 3. 采用ESD保护电路:在设计电路时,引入一些ESD保护电路,能够有效地减小ESD对电路的影响。例如采用Zener二极管、TVS二极管等保护电路。 在总体设计中,需要综合以上措施,采用一些适合的方案来消除ESD对电路的干扰。同时,在实际使用中,也需要对电路进行定期维护和检测,保证电路的正常运行。在电子技术的快速发展中,ESD防护的问题只会越来越重要,只有对其进行深入的研究和应用,才能更好地保证电子设备的稳定运行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值