ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Java类加载-双亲委派

2022-02-10 10:01:54  阅读:153  来源: 互联网

标签:委派 Java jar 双亲 class 加载


Java类加载-双亲委派

目录

类加载器

Bootstrap ClassLoader

该类加载器由C++实现的。负责加载Java基础类,对应加载的文件是%JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar和class等

Extension ClassLoader

继承URLClassLoader。对应加载的文件是%JRE_HOME/lib/ext 目录下的jar和class等

App ClassLoader

继承URLClassLoader。对应加载的应用程序classpath目录下的所有jar和class等

Custom ClassLoader

由Java实现。我们可以自定义类加载器,并可以加载指定路径下的class文件

双亲委派

作用:

  1. 防止重复加载同一个class
  2. 保证核心class不被篡改
    例如:可以避免有人自定义一个有破坏功能的java.lang.Integer被加载。这样可以有效的防止核心Java API被篡改。

机制:

事实上,类的加载分两步,第一步是先检查 逐层类加载器检查是不是加载过这个类,检查完,这个类就在 引导类加载器里了,发现这几层类加载器都没加载过这个类,这时候开始第二步,即从引导类加载器检查,这个类是不是 这个加载器负责加载的,如果是,就直接加载,如果不是就让下一层去尝试加载,如果到了最基层的类加载器都加载不了这个类,就报 classNotFound

经典问题

  1. 什么是双亲委派?
    当一个类加载器收到了类加载的请求的时候,他不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载。只有父加载器无法加载这个类的时候,才会由当前这个加载器来负责类的加载。一个用户自定义的类,如com.hollis.ClassHollis 是无论如何也不会被Bootstrap和Extention加载器加载的,一般认为上一层加载器是下一层加载器的父加载器,那么,除了BootstrapClassLoader之外,所有的加载器都是有父加载器的。。

  2. 为什么需要双亲委派,不委派有什么问题?
    首先,通过委派的方式,可以避免类的重复加载,当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。

    可以避免有人自定义一个有破坏功能的java.lang.Integer被加载。这样可以有效的防止核心Java API被篡改。

  3. 父子加载器"之间的关系是继承吗?
    双亲委派模型中,类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码的。

  4. 双亲委派是怎么实现的?
    java.lang.ClassLoader的loadClass()方法之中

    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // First, check if the class has already been loaded
                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) {
                        // ClassNotFoundException thrown if class not found
                        // from the non-null parent class loader
                    }
    
                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        long t1 = System.nanoTime();
                        c = findClass(name);
    
                        // this is the defining class loader; record the stats
                        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }
    

    代码不难理解,主要就是以下几个步骤:

    1、先检查类是否已经被加载过 2、若没有加载则调用父加载器的loadClass()方法进行加载 3、若父加载器为空则默认使用启动类加载器作为父加载器。 4、如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。

  5. 如何主动破坏双亲委派机制?
    想要破坏这种机制,那么就自定义一个类加载器,重写其中的loadClass方法,使其不进行双亲委派即可。

  6. 双亲委派被破坏的例子
    第一种,JDK1.2之前用户自定义的类加载器,第二种,是JNDI、JDBC等需要加载SPI接口实现类的情况,第三种,实现热插拔部署工具,第四种,tomcat等web容器;第五种,OSGI等模块化技术的应用

  7. 为什么JNDI,JDBC等需要破坏双亲委派?

  8. 我们日常开发中,大多数时候会通过API的方式调用Java提供的那些基础类,这些基础类时被Bootstrap加载的。

    但是,调用方式除了API之外,还有一种SPI的方式。

    如典型的JDBC服务,我们通常通过以下方式创建数据库连接:

    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql", "root", "1234");
    

    在以上代码执行之前,DriverManager会先被类加载器加载,因为java.sql.DriverManager类是位于rt.jar下面的 ,所以他会被根加载器加载。类加载时,会执行该类的静态方法。其中有一段关键的代码是:

    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.**class**);
    

    这段代码,会尝试加载classpath下面的所有实现了Driver接口的实现类。

    那么,问题就来了。

    DriverManager是被根加载器加载的,那么在加载时遇到以上代码,会尝试加载所有Driver的实现类,但是这些实现类基本都是第三方提供的,根据双亲委派原则,第三方的类不能被根加载器加载。

    那么,怎么解决这个问题呢?

    于是,就在JDBC中通过引入ThreadContextClassLoader(线程上下文加载器,默认情况下是AppClassLoader)的方式破坏了双亲委派原则。

    我们深入到ServiceLoader.load方法就可以看到:

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
    

    第一行,获取当前线程的线程上下⽂类加载器 AppClassLoader,⽤于加载 classpath 中的具体实现类。

  9. 为什么Tomcat要破坏双亲委派

    我们知道,Tomcat是web容器,那么一个web容器可能需要部署多个应用程序。

    不同的应用程序可能会依赖同一个第三方类库的不同版本,但是不同版本的类库中某一个类的全路径名可能是一样的。

    如多个应用都要依赖hollis.jar,但是A应用需要依赖1.0.0版本,但是B应用需要依赖1.0.1版本。这两个版本中都有一个类是com.hollis.Test.class。

    如果采用默认的双亲委派类加载机制,那么是无法加载多个相同的类。

    所以,Tomcat破坏双亲委派原则,提供隔离的机制,为每个web容器单独提供一个WebAppClassLoader加载器。

    Tomcat的类加载机制:为了实现隔离性,优先加载 Web 应用自己定义的类,所以没有遵照双亲委派的约定,每一个应用自己的类加载器——WebAppClassLoader负责加载本身的目录下的class文件,加载不到时再交给CommonClassLoader加载,这和双亲委派刚好相反

标签:委派,Java,jar,双亲,class,加载
来源: https://www.cnblogs.com/henuqin/p/15877897.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有