类加载器与双亲委派

类加载器与双亲委派

上一章讲了一下类的生命周期与加载过程,下面接着学习理解类的加载器与经常听到的双亲委派机制是个什么。

类加载器

jvm的类加载器,是通过类的全限定名(绝对路径)来查找需要被加载的类。

java中判断两个类是否“相等”,前提条件是这个两个类必须由同一个类加载器加载。如果两个类分别由不同的类加载加载,即时两个类源自同一个class文件,也一定不是“相等”的。

启动类加载器(Bootstrap Class Loader)

  • 这个类是由C++实现,是虚拟机的一部分。主要是将<JAVA_HOME>/lib目录里的、或者-Xbootclasspath参数所指定路径中的,能够被java虚拟机识别的类库加载到虚拟机中。比如rt.jar等核心类库。
  • 这里无论是<JAVA_HOME>/lib目录里的还是-Xbootclasspath参数所指定的,如果类库的名称不能够被java虚拟机识别,是无法被加载到虚拟机中的。
  • Bootstrap加载了Extension加载器和Application加载器。
System.out.println(java.lang.Object.class.getClassLoader());

上面代理打印java.lang.Object是由哪个类加载器加载的。打印的结果是null,说明Object类是由Bootstrap加载器加载的。

扩展类加载器(Extension Class Loader)

  • 加载扩展的jar包,是一种java系统类库的扩展机制,扩展java SE的功能。
  • 主要是将<JAVA_HOME>/lib/ext/*.jar目录里的、或者-Djava.ext.dir系统变量所指定路径中的所有类库加载到虚拟机中。
System.out.println(DNSNameService.class.getClassLoader());
System.out.println("ExtClassLoader的类加载器: " + DNSNameService.class.getClassLoader().getClass().getClassLoader());

上面两行代码:

  • 第一个打印DNSNameService的类加载器,打印结果:sun.misc.Launcher$ExtClassLoader@677327b6,所以DNSNameService的类加载器是Extension加载器。

  • 第二个打印DNSNameService的类加载器的加载器是什么,打印结果:ExtClassLoader的类加载器: null,可以得到,Extension类加载器是由Bootstrap类加载器加载的。

应用程序类加载器(Application Class Loader)

加载classpath指定路径的类库,如果应用程序中没有实现自定义类加载器(Custom Class Loader),那么一般情况下引用程序默认使用的就是Application类加载器。

然后代码:

public class TClassLoadDemo {
	    public static void main(String[] args) {
	        System.out.println(TClassLoadDemo.class.getClassLoader());
	        System.out.println("AppClassLoader的类加载器: " + TClassLoadDemo.class.getClassLoader().getClass().getClassLoader());
	    }
    }
  • 第一个打印TClassLoadDemo的类加载器,打印结果:sun.misc.Launcher$AppClassLoader@18b4aac2,由Application加载器加载到jvm内存的。

  • 第二个打印TClassLoadDemo的类加载器是由谁加载的,结果:AppClassLoader的类加载器: null,所以,Application加载器是由Bootstrap类加载器加载的。

自定义类加载器(Custom Class Loader)

就是自己实现一个类加载器,加载自己指定路径下的class文件。

双亲委派机制

先看一个伪代码:

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        // 首先,检查请求的类是否已经被加载过了
        Class c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 如果父类加载器抛出ClassNotFoundException
                // 说明父类加载器无法完成加载请求
            }
            if (c == null) {
                // 在父类加载器无法加载时
                // 再调用本身的findClass方法来进行类加载
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
  • 一个类加载器收到去加载一个类的申请时候,首先判断自己是否已经加载过这个类,如果已加载过,直接返回,如果未被加载则调用父类的加载器。每一个层次的类加载器都是如此。

  • 父类加载器反馈自己无法加载这个类的时候,子加载器才会尝试自己去加载这个类。每一个层次的类加载器都是如此。

在这里插入图片描述

这里我将自定义加载器也画进去了,流程跟结果是一样的,如果应用程序没有实现了自定义类加载器,那么Applicatoion类加载器就是最底层的那一个加载器

上图:
(1) 左边由下而上的箭头,表示子类收到一个加载类的请求,并去询问父类是否加载该类。

  1. 如果实现了Custom类加载器收到加载某类,判断自己是否已经加载了该类,如果加载了则返回该类,如果未加载则委托Application类加载器去加载。

  2. Applicatoion类加载器收到了加载类的委托,先判断自己是否已经加载了该类,如果加载了则返回该类给Custom类加载器,如果没有加载则委托Extension类加载器去加载。

  3. Extension类加载器收到了加载类的委托,先判断自己是否已经加载了该类,如果加载了则返回该类给Applicatoion类加载器,如果没有加载则委托Bootstrap类加载器去加载。

  4. Bootstrap类加载器收到了加载该类的委托,先判断自己是否已经加载了该类,如果加载了则返回该类给Extension类加载器。如果没有加载,就是右边自上而下的箭头流程了。

(2) 右边自上而下的箭头,表示父类查找并判断该类是否由自己加载,如果自己可以加载则加载成功后返给子类,如果不由自己加载,则返回null表示自己无法加载该类,让子类自己加载。

  1. Bootstrap类加载器首先判断该类是否在<JAVA_HOME>/lib目录里,或者-Xbootclasspath参数指定的路径之下,如果在,则自己加载该类。如果不在,则表示这个类不在Bootstrap加载器的加载范围内,则向子类Extension加载器返回null表示自己无法加载,让Extension自己加载。

  2. Extension加载器收到Bootstrap返回null之后,自己尝试加载该类。首先判断是否在自己的加载范围之类(是否在<JAVA_HOME>/lib/ext/*.jar目录里、或者-Djava.ext.dir系统变量所指定路径中),如果是,则加载并返回给子类,如果不是在自己的加载范围内,则向Application加载器返回null表示自己无能为力,让Application自己加载。

  3. Applicatoion类加载器也是同样的如此,先判断自己是否能加载,如果不能,返回null;如果能,返回加载信息。

  4. Custom类加载器同样如此,只是它是返给调用方,无论是异常还是正常加载信息。

双亲委派的作用

  • 避免类的重复加载。当父类加载器加载了该类时,子类加载器就不会再次加载。

  • 防止java核心jar被随意替换。

这里重要的是安全问题,也就是防止核心jar被随意替换。比如,自己实现一个java.lang.String类,然后让JVM加载,类加载器逐级往上查找判断是否被加载,而在启动类加载器(Bootstrap)的时候,发现该java.lang.String已经被加载过了,不需要加载,直接返回,所以自己实现的java.lang.String就没被加载到。

当然,我们在实现代码的时候,是不能命名一个包路径为java.lang的,因为java.lang目录里面存放的都是java的核心jar包,是不允许应用程序自定义java.lang这个包名的,强行定义,在加载时会抛:java.lang.SecurityException: Prohibited package name: java.lang

实现一个自定义类加载器

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        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) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

上面这个的类加载器的源码,不能看出loadClass这个方法实现的是双亲委派机制,并不是真正的进行类加载操作,真正的类加载操作是在c = findClass(name);中的findClass方法。
然后再看看findClass的源码,只有一句throw new ClassNotFoundException(name);,需要自己实现。

protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

所以自定义类加载器只需要重写findClass方法即可。

  • 需要加载的class文件未在classpath指定路径之下
public class MyClassLoader extends ClassLoader{

    private final String classPath = "C:\\Users\\Administrator\\Desktop\\delete\\learn\\";

    private final String fileSuffix = ".class";

    public static void main(String[] args) throws Exception {
        Object instance = new MyClassLoader().loadClass("com.yl.test.class_load.ClassLoadDemo").newInstance();
        System.out.println(instance.getClass());
        System.out.println(instance.getClass().getClassLoader());
    }

    public static void classLoad() throws ClassNotFoundException {
        Class<?> aClass = new MyClassLoader().getClass().getClassLoader().loadClass("com.yl.test.class_load.TClassLoadDemo");
        System.out.println(aClass.getClassLoader());
        System.out.println(aClass.getName());
    }

    /**
     * 重写的findClass
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] arr = loadClassAsByteArr(name);
        return super.defineClass(name, arr, 0, arr.length);
    }

    /**
     * 读取指定目录下的class文件,作为二进制字节流
     * @param name
     * @return 二进制字节流
     * @throws ClassNotFoundException
     */
    private byte[] loadClassAsByteArr(String name) throws ClassNotFoundException {
        if (null == classPath || null == name){
            throw new ClassNotFoundException("类路径不能为空!");
        }
        String path = classPath + name.replaceAll("[.]", Matcher.quoteReplacement(File.separator)) + fileSuffix;

        FileInputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            in = new FileInputStream(path);
            out = new ByteArrayOutputStream();

            byte[] buffer = new byte[1024];
            int size = 0;
            while ((size = in.read(buffer)) != -1) {
                out.write(buffer, 0, size);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return out.toByteArray();
    }

}

我就直接上代码了,注释都有。

  • 如果需要加载的类就在classpath指定路径下面。那么还可以简单些
Class<?> aClass = new MyClassLoader().getClass().getClassLoader().loadClass("com.yl.test.class_load.TClassLoadDemo");

首先获取MyClassLoader的类加载器:MyClassLoader是由application类加载加载的,那么直接调用application类加载的loadClass方法即可。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值