类加载器和双亲委派机制

前言:在上文反射面试题中,提到JDBC使用反射来加载驱动程序,就是加载时运行了statci代码块。本文来主要讨论一下与此相关的类加载器和双亲委派机制。

标题:《老大难的 Java ClassLoader,到了该彻底理解它的时候了》 作者:codehole_ 链接:https://blog.csdn.net/codehole_/article/details/100892463

标题:《Hotspot 类加载器Java源码解析》 作者:孙大圣666 链接:https://blog.csdn.net/qq_31865983/article/details/100173173

下面是loadClass方法(所有加载器都一样,)的源码

复制代码
protected synchronized Class<?> loadClass(String name,boolean resolve)throws ClassNotFoundException{
        // 首先找缓存是否有 class
        Class c=findLoadedClass(name);
        if(c==null){
        //没有判断有没有父类
        try{
        if(parent!=null){
        //有的话,用父类递归获取 class
        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){
        // 如果还是没有找到,调用 findClass(name)去找这个类
        c=findClass(name);}
        }
        if(resolve){
        resolveClass(c);}
        return c;
        }
复制代码

很明显的步骤:1.现在内存里找,没有找到委派到父类去找2.没有父类去bootstrap里找3.父类都没有找到调用自己的findClass查找,如下图,上层的父类加载器无法委派到下层

 

 

 

  

还是从面试题入手:

1.有哪些类加载器?

  1)BootstrapClassLoader 负责加载 JVM 运行时核心类,这些类位于 JAVA_HOME/lib/rt.jar 文件中,我们常用内置库 java.xxx.* 都在里面,比如 java.util.*、java.io.*、java.nio.*、java.lang.* 等等。这个 ClassLoader 比较特殊,它是由 C 代码实现的,我们将它称之为「根加载器」。

  2)ExtensionClassLoader 负责加载 JVM 扩展类,比如 swing 系列、内置的 js 引擎、xml 解析器 等等,这些库名通常以 javax 开头,它们的 jar 包位于 JAVA_HOME/lib/ext/*.jar 中,有很多 jar 包。

  3)AppClassLoader 才是直接面向我们用户的加载器,它会加载 Classpath 环境变量里定义的路径中的 jar 包和目录。我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的

2.什么是双亲委派机制?

  上面的源代码和图里里的逻辑判断流程就是双亲委派机制,子类加载器在加载一个类的时候,总是先递归调用父类那里加载,父类加载不到,再从本加载器加载。

3.双亲委派机制起到什么作用?

  双亲委派机制是JVM的一种安全机制,一是避免了类的重复加载,二是避免了核心API被篡改。

4.如果父类加载器想调用子类加载器加载的类怎么办(破坏双亲委派机制)?

  常见的例子JDBC:在Java应用中存在着很多服务提供者接口(Service Provider Interface,SPI),这些接口允许第三方为它们提供实现, 如常见的 SPI 有 JDBC、JNDI等,这些 SPI 的接口属于 Java 核心库,一般存在rt.jar包中,由Bootstrap类加载器加载, 而 SPI 的第三方实现代码则是作为Java应用所依赖的 jar 包被存放在classpath路径下, 由于SPI接口中的代码经常需要加载具体的第三方实现类并调用其相关方法,但SPI的核心接口类是由引导类加载器来加载的, 而Bootstrap类加载器无法直接加载SPI的实现类,同时由于双亲委派模式的存在,Bootstrap类加载器也无法反向委托AppClassLoader加载器SPI的实现类。

  通过线程的ContextClassLoader(ServiceLoader)上下文类加载器实现。

  具体可以参考:https://www.jianshu.com/p/bd94ae0d916ehttps://blog.csdn.net/weixin_37195606/article/details/82805300

5.类的初始化步骤

  1)如果这个类还没有被加载和链接,那先进行加载和链接;

  2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口);

  3)假如类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。

4、描述一下 JVM 加载 class?

JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。

  1)由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。

  2)当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。

  3)当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;如果类中存在初始化语句,就依次执行这些初始化语句。

  4)类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。

  5)从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。

   以上两题可结合参考文章:https://blog.csdn.net/noaman_wgs/article/details/74489549

6.父类子类含静态代码块加载顺序?

  结论:父类静态代码(包括静态字段)-子类静态代码-父类代码块-父类构造方法-子类代码块-子类构造方法

  可参考文章:https://www.cnblogs.com/zhuitian/p/12104614.html

posted @   小皮睡不醒  阅读(81)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示