双亲委派机制

首先提到一个概念:类加载器(Class Loader)

我们在IDE中编写好的Java程序会被编译器编译成.class文件(字节码文件),然后通过类加载器来负责将这些.class文件加载到JVM中执行。

JVM中提供了三层的ClassLoader:

  • Bootstrap ClassLoader:主要负责加载核心的类库(java.lang.*)等,构造ExtClassLoader和AppClassLoader。
  • ExtClassLoader:主要负责加载jre/lib/ext目录下的扩展类。
  • AppClassLoader:主要负责加载应用程序的主函数类。

那么,当我们写的Hello.java编译形成的Hello.class文件,又是如何被JVM所加载的呢?

首先打开java.lang包下的ClassLoader类,找到loadClass方法:

public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}

//-------------------------------------------------------------------------

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 {
                    //不存在父加载器,使用启动类加载器 BootstrapClassLoader加载
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                //抛出异常说明父类加载器无法完成加载请求
            }

            
            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;
    }
}

下面的图描述了这个过程:

img

​ 当一个Hello.class要被加载时,**在不考虑自定义类加载器的情况下,**首先会在AppClassLoader中检查是否已经被加载过了,如果没有被加载过,会交由上层父加载器进行处理,同样,父加载器也是先判断是否已经被加载过了,如果没有进一步交由上一层类加载器,直到BootstrapClassLoader,此时已经没有类加载器了,这时候考虑自己是否能够加载,如果无法加载,就会下沉交由子加载器,一直到最底层。如果到达最底部加载器都没办法加载,就会抛出ClassNotFoundException。

那么为什么要有这种机制呢?

首先来看这段程序:我们创建一个类String,和系统自带的String类同名同包,然后运行main方法,猜一猜会有什么效果?

package java.lang;

public class String {

    @Override
    public String toString() {
        return "helloWorld";
    }

    public static void main(String[] args) {
        String str = new String();
        System.out.println(str.toString());
    }
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-48G88M4t-1675132640203)(C:\Users\OYMN\AppData\Roaming\Typora\typora-user-images\image-20220221113551765.png)]

报错了?找不到main方法?,奇怪,main方法不都有吗。。。

这就涉及到这篇文章的重点了,双亲委派机制有什么用?

通过前面的知识,我们不妨来思考思考。这个String.java文件被编译成字节码文件后,首先会先进入AppClassLoader,然后一步一步交由上一层加载器,直到BootstrapClassLoader,它把这个类认为成了Java自带的String类,下面的类加载器也就没有机会再去加载了。因此自始自终JVM中存在的关于String的相关信息都是Java自带的那个,而不是我们自定义的,自带的String类自然没有main方法,所以程序才会报错。

这也说明了双亲委派机制的重要好处:如果有人想要替换系统级别的类,想要篡改它的实现,在这种机制下会先被BootstrapClassLoader加载,而BootstrapClassLoader加载的都是系统级别的类,自然会忽视我们自定义的类,从一定程度上防止了危险代码的植入。并且,也保证了Java的核心类不会被篡改,保证Java程序的稳定运行,例如上面我们自定义了一个java.lang.String类,那么程序运行时,系统就会出现不同的String类,产生安全隐患。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值