• 第五章 类加载器ClassLoader源码解析


    说明:了解ClassLoader前,先了解 第四章 类加载机制

    1、ClassLoader作用

    • 类加载流程的"加载"阶段是由类加载器完成的。

    2、类加载器结构

    结构:BootstrapClassLoader(祖父)-->ExtClassLoader(爷爷)-->AppClassLoader(也称为SystemClassLoader)(爸爸)-->自定义类加载器(儿子

    关系:看括号中的排位;彼此相邻的两个为父子关系,前为父,后为子

    2.1、BootstrapClassLoader

    • 下边简称为boot
    • C++编写
    • 为ExtClassLoader的父类,但是通过ExtClassLoader的getParent()获取到的是null(在类加载器部分:null就是指boot)
    • 主要加载:E:Javajdk1.6jrelib*.jar(最重要的就是:rt.jar)

    2.2、ExtClassLoader:

    • 下边简称为ext
    • java编写,位于sun.misc包下,该包在你导入源代码的时候是没有的,需要重新去下
    • 主要加载:E:Javajdk1.6jrelibext*.jar(eg.dnsns.jar)

    2.3、AppClassLoader:

    • 下边简称为app
    • java编写,位于sun.misc包下
    • 主要加载:类路径下的jar

    2.4、自定义类加载器:

    • 下边简称为custom
    • 自己编写的类加载器,需要继承ClassLoader类或URLClassLoader,并至少重写其中的findClass(String name)方法,若想打破双亲委托机制,需要重写loadClass方法
    • 主要加载:自己指定路径的class文件

    3、全盘负责机制

    概念:假设ClassLoaderA要加载class B,但是B引用了class C,那么ClassLoaderA先要加载C,再加载B,"全盘"的意思就是,加载B的类加载器A,也会加载B所引用的类

    4、双亲委托机制

    这也是类加载器加载一个类的整个过程

    过程:假设我现在从类路径下加载一个类A,

    1)那么app会先查找是否加载过A,若有,直接返回;

    2)若没有,去ext检查是否加载过A,若有,直接返回;

    3)若没有,去boot检查是否加载过A,若有,直接返回;

    4)若没有,那就boot加载,若在E:Javajdk1.6jrelib*.jar下找到了指定名称的类,则加载,结束;

    5)若没找到,boot加载失败;

    6)ext开始加载,若在E:Javajdk1.6jrelibext*.jar下找到了指定名称的类,则加载,结束;

    7)若没找到,ext加载失败;

    8)app加载,若在类路径下找到了指定名称的类,则加载,结束;

    9)若没有找到,抛出异常ClassNotFoundException

    注意:

    • 在上述过程中的1)2)3)4)6)8)后边,都要去判断是否需要进行"解析"过程 ("解析"见 第四章 类加载机制
    • 类的加载过程只有向上的双亲委托,没有向下的查询和加载,假设是ext在E:Javajdk1.6jrelibext*.jar下加载一个类,那么整个查询与加载的过程与app无关。
    • 假设A加载成功了,那么该类就会缓存在当前的类加载器实例对象C中,key是(A,C)(其中A是类的全类名,C是加载A的类加载器对象实例),value是对应的java.lang.Class对象
    • 上述的1)2)3)都是从相应的类加载器实例对象的缓存中进行查找
    • 进行缓存的目的是为了同一个类不被加载两次
    • 使用(A,C)做key是为了隔离类,假设现在有一个类加载器B也加载了A,key为(A,B),则这两个A是不同的A。这种情况怎么发生呢?
      • 假设有custom1、custom2两个自定义类加载器,他们是兄弟关系,同时加载A,这就是有可能的了

    总结

    • 从底向上检查是否加载过指定名称的类从顶向下加载该类。(在其中任何一个步骤成功之后,都会中止类加载过程)
    • 双亲委托的好处:假设自己编写了一个java.lang.Object类,编译后置于类路径下,此时在系统中就有两个Object类,一个是rt.jar的,一个是类路径下的,在类加载的过程中,当要按照全类名去加载Object类时,根据双亲委托,boot会加载rt.jar下的Object类,这是方法结束,即类路径下的Object类就没有加载了。这样保证了系统中类不混乱。

    5、源代码

     1     /**
     2      * 根据指定的binary name加载class。
     3      * 步驟:
     4      * 假设我现在从类路径下加载一个类A,
     5      * 1)那么app会先查找是否加载过A(findLoadedClass(name)),若有,直接返回;
     6      * 2)若没有,去ext检查是否加载过A(parent.loadClass(name, false)),若有,直接返回;
     7      * findBootstrapClassOrNull(name) 3)4)5)都是这个方法
     8      * 3)若没有,去boot检查是否加载过A,若有,直接返回;
     9      * 4)若没有,那就boot加载,若在E:Javajdk1.6jrelib*.jar下找到了指定名称的类,则加载,结束;
    10      * 5)若没找到,boot加载失败;
    11      * findClass(name) 6)7)8)9)都是这个方法
    12      * 在findClass中调用了defineClass方法,该方法会生成当前类的java.lang.Class对象
    13      * 6)ext开始加载,若在E:Javajdk1.6jrelibext*.jar下找到了指定名称的类,则加载,结束;
    14      * 7)若没找到,ext加载失败;
    15      * 8)app加载,若在类路径下找到了指定名称的类,则加载,结束;
    16      * 9)若没有找到,抛出异常ClassNotFoundException
    17      * 注意:在上述过程中的1)2)3)4)6)8)后边,都要去判断是否需要进行"解析"过程
    18      */
    19     protected synchronized Class<?> loadClass(String name, boolean resolve)
    20             throws ClassNotFoundException {
    21         Class c = findLoadedClass(name);//检查要加载的类是不是已经被加载了
    22         if (c == null) {//没有被加载过
    23             try {
    24                 if (parent != null) {
    25                     //如果父加载器不是boot,递归调用loadClass(name, false)
    26                     c = parent.loadClass(name, false);
    27                 } else {//父加载器是boot
    28                     /*
    29                      * 返回一个由boot加载过的类;3)
    30                      * 若没有,就去试着在E:Javajdk1.6jrelib*.jar下查找 4)
    31                      * 若在bootstrap class loader的查找范围内没有查找到该类,则返回null 5)
    32                      */
    33                     c = findBootstrapClassOrNull(name);
    34                 }
    35             } catch (ClassNotFoundException e) {
    36                 //父类加载器无法完成加载请求
    37             }
    38             if (c == null) {
    39                 //如果父类加载器未找到,再调用本身(这个本身包括ext和app)的findClass(name)来查找类
    40                 c = findClass(name);
    41             }
    42         }
    43         if (resolve) {
    44             resolveClass(c);
    45         }
    46         return c;
    47     }
    View Code

    说明:

    • 该段代码中引用的大部分方法实质上都是native方法
    • 其中findClass方法的类定义如下:
          /**
           * 查找指定binary name的类
           * 该类应该被ClassLoader的实现类重写
           */
          protected Class<?> findClass(String name) throws ClassNotFoundException {
              throw new ClassNotFoundException(name);
          }
      View Code
    • 关于findClass可以查看URLClassLoader.findClass(final String name),其中引用了defineClass方法,在该方法中将二进制字节流转换为了java.lang.Class对象

    采用模板模式,我们实现自定义类加载器:

    public class UserDefineClassLoader extends ClassLoader {
        /**
         * 自定义加载器的名称
         */
        private String loaderName;
    
        /**
         * 指定自定义加载器的名称
         */
        public UserDefineClassLoader(String loaderName) {
            // 父类加载器 this(checkCreateClassLoader(), getSystemClassLoader())
            super();
            this.loaderName = loaderName;
        }
    
        /**
         * 指定父类加载器
         */
        public UserDefineClassLoader(String loaderName, ClassLoader parent) {
            super(parent);
            this.loaderName = loaderName;
        }
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            // 1. 读取文件内容为byte[]
            byte[] classBytes = readClassDataFromFile("/Users/jigangzhao/Desktop/A.class");
            // 2. 将byte[]转化为Class
            Class<?> aClass = defineClass("classLoader.A", classBytes, 0, classBytes.length);
            return aClass;
        }
    }

     附:关于递归

    递归基于栈实现。

    上述的代码如果不清楚递归的意义是看不清的。

    解释:

    • app_loadClass()方法执行到ext_loadClass(),这时候对于app_loadClass()中剩余的findClass()会在栈中向下压;
    • 然后执行ext_loadClass(),当执行到findBootstrapClassOrNull(name),这时候ext_loadClass()中剩余的findClass()也会从栈顶向下压,此时ext_loadClass()_findClass()仅仅位于app_loadClass()_findClass()的上方;
    • 然后执行findBootstrapClassOrNull(name),当boot检测过后并且执行完加载后并且没成功,boot方法离开栈顶;
    • 然后执行此时栈顶的ext_loadClass()_findClass()
    • 然后执行此时栈顶的app_loadClass()_findClass()

    这样,就完成了双亲委托机制。

    注意点:

    类隔离机制

    1. 同一个类Dog可以加载两次(只要loader1和loader3不是父子关系即可,加载出的 Class 对象不同),不同运行空间内的类不能互相访问(eg. loader1和loader3不是父子关系,则Loader1加载的Dog不能访问lodaer3加载的Sample)
    2. 父类加载器无法访问到子类加载器加载的类,除非使用反射。Eg. Loader1 的父加载器是 系统类加载器,假设 Sample 类由 loader1 加载, 使用 loader1 的类 Test 是由系统类加载器加载的,例如下面这段代码属于 Test 类,那么如果直接使用注释部分的代码(即通过常规的方式使用 Sample 是不行的),必须通过反射。

     

  • 相关阅读:
    Hibernate 项目查询数据报 UnknownEntityTypeException
    C# 实现保留两位小数的方法
    在vs中安装和引用科学计算库 Math.NET Numerics
    C# 特性(Attribute)
    Modbus测试工具ModbusPoll与Modbus Slave使用方法
    StarUML3.0学习笔记
    转载:接近开关NPN型与PNP型的相互代替
    C#中(int)、int.Parse()、int.TryParse()和Convert.ToInt32()的区别
    DateTime.ToString() Patterns
    _056_根据年份判断十二生肖
  • 原文地址:https://www.cnblogs.com/java-zhao/p/5201291.html
Copyright © 2020-2023  润新知