最后更新于 2022 年 4 月 26 日

Featured image of post 双亲委派机制

双亲委派机制

关于双亲委派机制一些的问题

java类加载器

首先,我们知道,虚拟机在加载类的过程中需要使用类加载器进行加载,而在Java中,类加载器有很多,那么当JVM想要加载一个.class文件的时候,到底应该由哪个类加载器加载呢?

首先,我们需要知道的是,Java语言系统中支持以下4种类加载器:

Bootstrap ClassLoader (启动类加载器)
默认加载的是jdk\lib目录下jar中诸多类;
这个路径可以使用 -Xbootclasspath参数指定。

Extension ClassLoader (扩展类加载器)
默认加载jdk\lib\ext\目录下jar中诸多类;
这个路径可以使用 java.ext.dirs系统变量来更改。

Application ClassLoader (应用程序类加载器)
负责加载开发人员所编写的诸多类。

User ClassLoader (自定义类加载器)
当存在上述类加载器解决不了的特殊情况,或存在特殊要求时,可以自行实现类加载逻辑。

关系如图所示:

类加载器关系图
类加载器关系图

什么是双亲委派

其是在JDK1.2期间被引入的,而后陆续被推荐给开发者,到目前已经成为了最常用的类加载器实现方式了。 双亲委派整个过程分为以下几步:

  • 假设用户刚刚摸鱼写的Test类想进行加载,这个时候首先会发送给应用程序类加载器AppCloassLoader;

  • 然后AppClassLoader并不会直接去加载Test类,而是会委派于父类加载器完成此操作,也就是ExtClassLoader;

  • ExtClassLoader同样也不会直接去加载Test类,而是会继续委派于父类加载器完成,也就是BootstrapClassLoader;

  • BootstrapClassLoader这个时候已经到顶层了,没有父类加载器了,所以BootstrapClassLoader会在jdk/lib目录下去搜索是否存在,因为这里是用户自己写的Test类,是不会存在于jdk下的,所以这个时候会给子类加载器一个反馈。

  • ExtClassLoader收到父类加载器发送的反馈,知道了父类加载器并没有找到对应的类,爸爸靠不住,就只能自己来加载了,结果显而易见,自己也不行,没办法,只能给更下面的子类加载器了。

AppClassLoader收到父类加载器的反馈,顿时明白,原来爸爸虽然是爸爸,但是他终究不能管儿子的私事,所以这时候,AppClassLoader就自己尝试去加载。

总结:就是一个类从应用类加载器(Application ClassLoader)进入,会不断的向上找类加载器直到找到类加载器的顶层->启动类加载器(BootstrapClassLoader),然后查看有没有对应的类,如果没有就交给子类去加载。

为什么需要双亲委派机制

使用双亲委派模型,有一个很大的好处,就是避免原始类被覆盖的问题

比如,用户编写了一个Object类,放入程序中加载。

当没有双亲委派机制时,就会出现重复的Object类,会给开发人员造成很大的困扰,本来就只需要基于JDK开发就好了,现在还得把JDK中的类全记住,避免编写重复的类。

当存在双亲委派机制时呢,整个事情就不一样了,每次加载类时,都会遵循双亲委派机制,去问父类是否可以加载,如果可以呢,那就不需要再次加载了,这样事情就变得简单了。(老子走的路,小子不能走 )

另外,通过双亲委派的方式,还保证了安全性。因为Bootstrap ClassLoader在加载的时候,只会加载JAVA_HOME中的jar包里面的类,如java.lang.Integer,那么这个类是不会被随意替换的,除非有人跑到你的机器上, 破坏你的JDK。

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

总结:1.避免类的重复加载

2.防止核心类被篡改

如何主动破坏双亲委派机制?

首先看一下loadclass的源码:

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 {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。
                }
                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;
        }
    }

看过源码我们知道了双亲委派模型的实现,那么想要破坏双亲委派机制就很简单了。

因为他的双亲委派过程都是在loadClass方法中实现的,那么想要破坏这种机制,那么就自定义一个类加载器,重写其中的loadClass方法,使其不进行双亲委派即可。

总结:重写loadclass方法

双亲委派被破坏的例子

  • 第一种被破坏的情况是在双亲委派出现之前。
    由于双亲委派模型是在JDK1.2之后才被引入的,而在这之前已经有用户自定义类加载器在用了。所以,这些是没有遵守双亲委派原则的。

  • 第二种,是JNDI、JDBC等需要加载SPI接口实现类的情况。

  • 第三种是为了实现热插拔热部署工具。为了让代码动态生效而无需重启,实现方式时把模块连同类加载器一起换掉就实现了代码的热替换。

  • 第四种时tomcat等web容器的出现。

  • 第五种时OSGI、Jigsaw等模块化技术的应用。

Built with Hugo