双亲委派机制源码分析以及自定义类加载器

双亲委派

双亲委派:如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给”父类加载器“,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当”父类加载器“在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。

jvm中加载器层次关系:

启动类加载器BootStrapClassLoader --------> 扩展类加载器ExtensionClassLoader -------->
应用加载器AppClassLoader --------> 自定义加载器

  • BootStrapClassLoader:

    加载/lib/下的jar(包括Java核心类库rt.jar等其他),因为是C++写的,程序里面打印出来是null
    官方注释:Some implementations may use null to represent the bootstrap class loader

  • ExtensionClassLoader:

    加载/lib/ext/下的jar,Java实现,可以在程序里里面获取

  • AppClassLoader:

    又叫SystemClassLoader,classpath下面的即我们写的代码,以及第三方引入的依赖,都是他加载的

示例代码:

package day20200228;


import com.sun.nio.zipfs.ZipFileSystem;
import junit.extensions.TestDecorator;
import sun.net.spi.nameservice.dns.DNSNameService;

import javax.crypto.Cipher;

/**
 * @Author: xiaoshijiu
 * @Date: 2020/2/29
 * @Description: $value$
 */
public class TestClassLoad {

    public static void main(String[] args) {

        /**
         * BootStrapClassLoad
         */
        System.out.println(Object.class.getClassLoader());  // rt.jar
        System.out.println(String.class.getClassLoader());  // rt.jar
        System.out.println(Math.class.getClassLoader());    // rt.jar
        System.out.println(Cipher.class.getClassLoader());  // jce.jar

        System.out.println("================");

        /**
         * ExtensionClassLoad
         */
        System.out.println(ZipFileSystem.class.getClassLoader());  // ext/zipfs.jar
        System.out.println(DNSNameService.class.getClassLoader());  // ext/dnsns.jar

        System.out.println("================");

        /**
         * AppClassLoad/SystemClassLoad   
         */
        System.out.println(TestClassLoad.class.getClassLoader());   // 自己写的
        System.out.println(ClassLoadingProcessStatic.class.getClassLoader());   // 自己写的
        System.out.println(TestDecorator.class.getClassLoader());   // 引入的junit依赖中的
    }

}

运行结果
双亲委派机制源码分析以及自定义类加载器_第1张图片

双亲委派的源码实现

java.lang.ClassLoader中的,通过loadClass方法加载类,也就是双亲委派的具体实现

// name参数,即binary name,类的全类名
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,检查这个类是不是被加载过了;findLoadedClass方法检查
            Class<?> c = findLoadedClass(name);
            // 没有被加载过
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                    // ”父类“不为空,找父类加载器加载;loadClass方法:仍然调用该方法,即产生递归;
                        c = parent.loadClass(name, false);
                    } else {
                    // ”父类“为空,即代表”父类“为BootstrapClassLoader,找其加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                   //异常
                }

                if (c == null) {        
                    long t1 = System.nanoTime();
                    // 如果还没有加载到,c仍然为空,那么调用该加载器的findClass方法,去加载
                    c = findClass(name);

                    // 记录时间
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

上面的一步一步走下来,注释写得都很清楚,流程也是很清楚了;

再看看基类java.lang.ClassLoader中的findClass方法
双亲委派机制源码分析以及自定义类加载器_第2张图片
空方法,没有实现,所以要想自定义类加载器,必须要继承基类并重写该findClass方法

`findClass`方法将 binary name 变成 Class 对象
这里有一个关于binary name的解释:
$ 表示里面的内部类,$ 数字里面的第几个匿名内部类

双亲委派机制源码分析以及自定义类加载器_第3张图片

自定义类加载器

前面说了要想自定义类加载器,必须要继承基类并重写该findClass方法

findClass方法 将 binary name 变成 Class 对象
另外在结合: defineClass方法 可以将class文件内容的字节数组转成Class对象
defineClass方法 在基类`java.lang.ClassLoader`中有实现

这样一来,我们只需要 根据class文件名 binary name 获得 class文件内容的byte[]形式 
再根据defineClass方法,将byte[]数据变为Class对象,完成。
package day20200229;


import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * @Author: xiaoshijiu
 * @Date: 2020/2/29
 * @Description: 自定义 ClassLoader
 * 继承 基类ClassLoader,重写 findClass方法
 * findClass方法:将 binary name 变成 Class对象
 * 其中还需要另外一个方法 defineClass :可以将class文件的字节数组转成Class对象
 */
public class MyClassLoader extends ClassLoader {


    static String path;

    public MyClassLoader() {
        super();
    }

    // 指定 上级类加载器的构造方法
    public MyClassLoader(ClassLoader parent) {
        super(parent);
    }

    /**
     * @param name binary name
     */
    @Override
    protected Class<?> findClass(String name) {
        byte[] bytes = classToBytes(name);
        return defineClass(name, bytes, 0, bytes.length);
    }

    /**
     * 抽取出来一个私有方法,将class文件内容变成bytes数组
     *
     * @param name binary name
     */
    private byte[] classToBytes(String name) {
        FileInputStream inputStream = null;
        ByteArrayOutputStream outputStream = null;
        try {
            // 判空
            if (name == null || "".equals(name)) {
                return null;
            }
            // 加载指定path路径的class文件
            if (path == null) {
            	// 文件位置,.替换成/
                name = name.replace(".", "/") + ".class";
            } else {
                name = path + name.replace(".", "/") + ".class";
            } 

            inputStream = new FileInputStream(name);
            outputStream = new ByteArrayOutputStream();
            // 一次接受50字节
            byte[] bytes = new byte[50];
            int len;
            while ((len = inputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, len);
            }
            return outputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return null;
    }


 	 // 测试代码
	 public static void main(String[] args) throws ClassNotFoundException {
        MyClassLoader classLoader = new MyClassLoader();

        // classpath下的类(自己写的类),双亲委派机制,由AppClassLoader加载
        Class<?> c = classLoader.loadClass("day20200222.TestVolatile");
        System.out.println(c.getClassLoader());

        // 硬盘中的类(g:/day20190820.ListNodeSum.class),双亲都加载不了,由该自定义的加载器加载
        path = "g:/";
        Class<?> c2 = classLoader.loadClass("day20190820.ListNodeSum");
        System.out.println(c2.getClassLoader());
    }
}

上面两组测试代码:

  • 第一,day20200222.TestVolatile该类位于当前项目工程中,即classpath下,根据双亲委派机制,由AppClassLoader加载
  • 第二,day20190820.ListNodeSum位于g盘中,根据双亲委派机制,”父类加载器“均不能加载,最后反馈到该类加载器,其findClass方法可以正确加载,即自定义加载器完成加载

运行结果
双亲委派机制源码分析以及自定义类加载器_第4张图片

你可能感兴趣的:(JVM)