了解双亲委派吗?如何破坏双亲委派?o((⊙﹏⊙))o

我们都知道虚拟机在加载类的过程中是使用类加载器进行加载,Java中类加载器有多种,当JVM加载一个.class文件的时候,是由哪个类加载器加载呢?

双亲委派机制

当一个类加载器收到类加载的请求时,不会直接去加载指定的类,而是委托给自己的父加载器去加载(层级关系,不是继承哦),只有父加载器无法加载类时,才会由当前类加载器来负责加载

Java支持4种类加载器:

  • Bootstrap ClassLoader 启动类加载器

  • Extention ClassLoader 标准扩展类加载器

  • Application ClassLoader 应用类加载器

  • User ClassLoader 用户自定义类加载器

  • Bootstrap ClassLoader ,主要负责加载Java核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。

  • Extention ClassLoader,主要负责加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。

  • Application ClassLoader ,主要负责加载当前应用的classpath下的所有类

  • User ClassLoader , 用户自定义的类加载器,可加载指定路径的class文件

加载流程:

  1. 子类先委托父类加载

  2. 父类加载器有自己的加载范围,范围内没有找到,则不加载,并返回给子类

  3. 子类在收到父类无法加载的时候,才会自己去加载

类加载器加载范围:

  1. 启动类加载器(Bootstrap ClassLoader)

    C++实现,负责加载<JAVA_HOME>/lib下的类

  2. 扩展类加载器(Extension ClassLoader)

    Java实现,负责加载<JAVA_HOME>/lib/ext下的类

  3. 系统类加载器/应用程序类加载器(Application ClassLoader)

    代码默认由它来加载,ClassLoader.getSystemClassLoader返回的就是它

  4. public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }

为什么需要双亲委派?

优点:

  1. 可以避免类的重复加载,当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类

  2. 保证了安全性,因为Bootstrap ClassLoader在加载的时候,只会加载JAVA_HOME中的jar包里面的类,类不会被随意修改替换,防止JDK被篡改

双亲委派的实现

java.lang.ClassLoader.java

protected Class<?> loadClass(String name, boolean resolve)        throws ClassNotFoundException    {
   
           synchronized (getClassLoadingLock(name)) {
   
               // 检查类是否已经被加载过            Class<?> c = findLoadedClass(name);            if (c == null) {
   
                   long t0 = System.nanoTime();                try {
   
                      //先获取父类加载器,不为空则交给父类加载器                    if (parent != null) {
   
                           c = parent.loadClass(name, false);                    } else {
   
                       //bootstrap classloader的类加载器为null,则通过find方法来获得                        c = findBootstrapClassOrNull(name);                    }                } catch (ClassNotFoundException e) {
   
                       // ClassNotFoundException thrown if class not found                    // from the non-null parent class loader                }                if (c == null) {
   
                       // 如果仍没有获得该类,调用findClass找到类                    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;        }    }

如何破坏双亲委派机制

通过上面代码我们知道双亲委派机制是在loadClass方法中实现的,破坏双亲委派机制只需要定义类加载器

自定义类加载器

  • 遵守双亲委派机制
    继承ClassLoader,重写findClass()方法。

  • 破坏双亲委派机制
    继承ClassLoader,重写loadClass()方法。

protected Class<?> findClass(String name) throws ClassNotFoundException {

        throw new ClassNotFoundException(name);    }

破坏双亲委派机制

  • jdbc

    原生的JDBC中Driver驱动本身只是一个接口,并没有具体的实现,由不同数据库类型去实现。例如,MySQL的mysql-connector-java.jar中的Driver类具体实现的。 原生的JDBC中的类是放在rt.jar包的,是由启动类加载器进行类加载的,在JDBC中的Driver类中需要动态去加载不同数据库类型的Driver类,而mysql-connector-java.jar中的Driver类是用户自定义的,那启动类无法进行加载,需要由应用程序启动类去进行类加载。

        Class mysql = Class.forName("com.mysql.cj.jdbc.Driver");        System.out.println(mysql.getClassLoader());

JDK 自己为解决该问题,引入线程上下文类加载器,可以通过Thread的setContextClassLoader()进行设置

  • Tomcat要破坏双亲委派

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

猜你喜欢

转载自blog.csdn.net/feikillyou/article/details/113105441