zoukankan      html  css  js  c++  java
  • 第五章 类加载器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 是不行的),必须通过反射。

     

  • 相关阅读:
    51nod 1179 最大的最大公约数 (数论)
    POJ 3685 二分套二分
    POJ 3045 贪心
    LIC
    HDU 1029 Ignatius and the Princess IV
    HDU 1024 Max Sum Plus Plus
    HDU 2389 Rain on your Parade
    HDU 2819 Swap
    HDU 1281 棋盘游戏
    HDU 1083 Courses
  • 原文地址:https://www.cnblogs.com/java-zhao/p/5201291.html
Copyright © 2011-2022 走看看