Java中常见的类加载器及双亲委派机制的原理

相信不少的同学在面试的时候会被问到一个词:双亲委派,懂得同学懂,不懂的同学可能会尴尬一笑,那么今天咱们就来聊聊这个问题的原理,首先我们需要了解一下java中常见的几种类加载器。一、Java中常见的类加载器1.BootstrapClassLoader纯C++实现的类加载器,没有对应的Java类,主要加载的是jre/lib/目录下的核心库2.ExtClassLoader类的全名是…

大家好,又见面了,我是你们的朋友全栈君。

相信不少的同学在面试的时候会被问到一个词:双亲委派,懂得同学懂,不懂的同学可能会尴尬一笑,那么今天咱们就来聊聊这个问题的原理,首先我们需要了解一下java中常见的几种类加载器。

一、Java中常见的类加载器

  • 1.BootstrapClassLoader
    纯C++实现的类加载器,没有对应的Java类,主要加载的是jre/lib/目录下的核心库

  • 2.ExtClassLoader
    类的全名是sun.misc.Launcher$ExtClassLoader,主要加载的是jre/lib/ext/目录下的扩展包

  • 3.AppClassLoader
    类的全名是sun.misc.Launcher$AppClassLoader,主要加载的是CLASSPATH路径下的包

下面我们通过一些简单代码示例来验证一下上述的结论

首先创建一个Java类,在main方法里添加以下代码:

public class ClassLoaderTest { 
   
    public static void main(String[] args){ 
   
        //1.打印ClassLoaderTest的类加载器
        Class mainClass = ClassLoaderTest.class;
        ClassLoader mainLoader = mainClass.getClassLoader();
        System.out.println("mainLoader's Name : "+mainLoader.toString());
    }
}

我们在代码里得到类的一个class对象,然后通过它的getClassLoader方法得到一个ClassLoader对象,那么运行一下看看这个ClassLoader对象的名字是什么
在这里插入图片描述
可以看到红框里的类是AppClassLoader,路径是sun.misc.Launcher下的。

我们点进ClassLoader的源码中发现
在这里插入图片描述
它有个字段parent,返回值也是一个ClassLoader,并且提供了方法
在这里插入图片描述
那么我们通过这个字段打印一下parent对应的ClassLoader的名字,在代码中添加:

//2.通过getParent方法,获取mainLoader中的parent字段
ClassLoader parentLoader = mainLoader.getParent();
System.out.println("parentLoader's Name : "+parentLoader.toString());

运行看看结果:
在这里插入图片描述
可以看到AppClassLoader的parent得到的是ExtClassLoader,我们现在来打印一下ExtClassLoader的加载路径

//3.打印ExtClassLoader的加载路径
URL[] mUrlsExt = ((URLClassLoader)parentLoader).getURLs();
print(mUrlsExt);
/** * 打印url数组 * @param urls */
public static void print(URL[] urls){ 
   
    for (URL url : urls){ 
   
        System.out.println(url);
    }
}

在这里插入图片描述
可以看到输出结果,ExtClassLoader的加载路径加载的是jre/lib/ext/目录下的扩展包。

接下来我们对parentLoader调用getParent方法打印它对父加载器:

 //4.通过gerParent方法,获取parentLoader中parent字段
 ClassLoader parentLoader2 = parentLoader.getParent();
 System.out.println("parentLoader2's Name : "+parentLoader2);

运行看结果:
在这里插入图片描述
可以看到这里打印出来的结果为空。

因为BootstrapClassLoader是C++实现的,所以它没有对应的Java类,所以我们只能采取一些特殊手段来获取它的加载路径,前面我们发现AppClassLoader和ExtClassLoader都是Launcher这个类的内部类,而且Launcher提供了一个方法getBootstrapClassPath来获取BootstrapClassLoader的加载路径,看代码:

//5.打印BootstrapClassLoader的加载路径
try { 
   
    Class launcherClass = Class.forName("sun.misc.Launcher");
    Method methodGetClassPath = launcherClass.getDeclaredMethod("getBootstrapClassPath",null);
    if (null != methodGetClassPath){ 
   
        methodGetClassPath.setAccessible(true);
        Object object = methodGetClassPath.invoke(null,null);
        if (null != object){ 
   
            Method methodGetUrls = object.getClass().getDeclaredMethod("getURLs",null);
            if (null != methodGetUrls){ 
   
                methodGetUrls.setAccessible(true);
                URL[] mUrlBoot = (URL[]) methodGetUrls.invoke(object,null);
                print(mUrlBoot);
            }
        }
    }
} catch (ClassNotFoundException e) { 
   
    e.printStackTrace();
}

运行结果:
在这里插入图片描述
可以看到BootstrapClassLoader的加载路径是jre/lib目录,加载的是jre/lib/目录下的核心库,和开头说的也是一致的。

二、双亲委派机制原理

现在我们要来介绍一下,一个类加载器是如何加载一个类的。
首先看一段代码:

public class ClassLoaderDemo { 
   
    public static void main(String[] args){ 
   
        Class clazz = ClassLoaderDemo.class;
        ClassLoader loader = clazz.getClassLoader();
        System.out.println("loader's Name : "+loader.toString());
    }
}

这段代码的运行结果如果看完上面并了解的同学应该能很准确的说出结果:AppClassLoader

下面在来加一点代码:

Class listClass = List.class;
ClassLoader listLoader = listClass.getClassLoader();
System.out.println("listLoader's Name : "+listLoader.toString());

在这里插入图片描述
可以看到List的classLoader为空,那么为什么为空呢?

因为像List是属于jdk中的东西,而jdk其实是放在一个rt.jar包中,而这个包的路径是:
在这里插入图片描述
jre/lib/目录下,通过上面说的,jre/lib/这个目录的jar包应该是由BootstrapClassLoader负责加载的,而这个BootstrapClassLoader类加载器是C++实现的,没有对应的Java类,所以打印出的结果为null。

接下来我们查看一下Java中ClassLoader这个类的源码是如何加载一个类的。

在ClassLoader源码中有一个loadClass方法,调用的是重载方法
在这里插入图片描述
在这里插入图片描述
我们需要重点关注红框部分的1-4这些代码,接下来捋一下逻辑:
1.第一步:走到代码1检查是否已经加载过这个类,如果加载过就直接返回,不走c==null逻辑里的代码,流程结束。
2.第二步:如果没加载过则进入到c == null的逻辑判断里,判断parent是否为空,如果不为空,就交由parent执行loadClass操作,否则执行findBootstrapClassOrNull方法。

这里我们以之前的例子来一步步看:

1)如果是AppClassLoader类加载器,执行loadClass方法时,parent不为空,parent是ExtClassLoader,现在由ExtClassLoader执行loadClass方法也就是代码2,继续走到里面后,判断parent是否为null,因为ExtClassLoader的parent==null,所以会走到代码3。
2)走到代码3后,执行findBootstrapClassOrNull方法,在该方法中调用findBootstrapClass方法,注意一个修饰符native,说明这个是native方法,因为BootstrapClassLoader是C++实现的,所以这里可以理解了。
在这里插入图片描述
3)如果前面代码1,2,3都执行完了,执行过程中出现了异常,这个时候c == null,会走到代码4中,执行findClass方法
在这里插入图片描述
findClass方法中什么都没做,只是抛出一个ClassNotFoundException异常,相信这个异常大家见得也比较多了。

整个的loadClass流程已经执行完了,用一张图来总结一下流程
在这里插入图片描述

三、为什么要引入双亲委派(父委托)机制?

由一段代码入手,接着刚才的代码,我们创建一个包,名为java.util,在这个包名下创建一个类ArrayList,写一段静态代码,然后在main方法中饮用ArrayList这个类,运行看看结果:

public class ClassLoaderDemo { 
   
    static { 
   
        System.out.println("ClassLoaderDemo is load!");
    }
    public static void main(String[] args){ 
   
        Class clazz = ClassLoaderDemo.class;
        ClassLoader loader = clazz.getClassLoader();
        System.out.println("loader's Name : "+loader.toString());

        Class listClass = ArrayList.class;
        ClassLoader listLoader = listClass.getClassLoader();
        System.out.println("listLoader's Name : "+listLoader.toString());
    }
}

自己创建的ArrayList

package java.util;

public class ArrayList { 
   
    static { 
   
        System.out.println("ArrayList is load!");
    }
}

运行结果:
在这里插入图片描述
可以看到这里并没有加载我们工程里的ArrayList,还是加载的jdk里的ArrayList,及时我们的工程里创建里一个和jdk里完全一样的类,仍然不会被加载。

那么这样由什么好处呢?
采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。


好啦,写到这里关于Java中常见的三种类加载器还有双亲委派(父委托)机制的说明已经介绍完了,希望对大家有所帮助!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/127026.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • MapperScan注解详解[通俗易懂]

    MapperScan注解详解[通俗易懂]1、@Mapper注解:作用:在接口类上添加了@Mapper,在编译之后会生成相应的接口实现类添加位置:接口类上面@MapperpublicinterfaceUserDAO{  //代码}如果想要每个接口都要变成实现类,那么需要在每个接口类上加上@Mapper注解,比较麻烦,解决这个问题用@MapperScan2、@MapperScan作用:指定…

  • Ubuntu安装和配置ssh

    Ubuntu安装和配置ssh因为配置pypbc环境,需要windows系统下PycharmSSH连接虚拟机python环境1.安装ssh服务器sudoaptinstallopenssh-server2.安装ssh客

  • 成果被他人窃取_工作窃取模式

    成果被他人窃取_工作窃取模式什么是ForkJoin、ForkJoin分支合并、ForkJoin工作窃取、ForkJoin大数据求和计算什么是ForkJoin?ForkJoin:分支合并ForkJoin特点:工作窃取如何让使用ForkJoinForkJoin求和计算Demo什么是ForkJoin?ForkJoin(分支合并)是jdk1.7之后出来的,并行执行任务,提高效率,用在大数据量场景下。大数据:MapReduce(把大任务拆分成多个小任务,怎么拆分用到了二分算法),每个小任务得出自己的结果,之后再把结果汇总,汇总的过程就是

  • Unity键盘钩子[通俗易懂]

    Unity键盘钩子[通俗易懂]http://blog.csdn.net/qq452626100/article/details/52398830privatestaticintKeyboardHookProc(intnCode,Int32wParam,IntPtrlParam){ if(nCode==HC_ACTION ) { varkc=(KeyCode)(wParam+97-65)

  • 银行机构代码_工商银行怎么查12位行号

    银行机构代码_工商银行怎么查12位行号因为做到绑定银行卡的时候,需要定义一下银行卡的代号。  找了一下这方面的资源: 银行机构代码  央行颁发支付系统银行行别、行号业务标准,支付系统银行行别代码采取类别编码方法,实行3位定长数字,由类别代码和顺序编码组成。其中第一位为类别代码,用于区分不同种类的银行机构,便于金融统计数据的提取;第二、三位为顺序编码,用于标识每一家银行机构。  银行行别代码结构:  一、类别代码…

    2022年10月24日
  • java程序设计图书管理系统源码(java图书管理系统设计报告)

    图书管理系统需实现的功能如下:(1)用户管理:包括用户的注册于登录。(2)图书管理:包括录入、查询、修改和删除图书信息。(3)借书:包括借阅图书和查看借书记录。(4)还书:包括还书和查看还书记录。(5)为了保证系统安全,进入系统时,对用户登录的密码进行加密与解密。源码、课程设计报告、数据库表图的百度网盘链接:https://pan.baidu.com/s…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号