JavaSE-ClassLoader类加载器与双亲委托模式

        我们已经知道Class类是描述类的信息的类,在我们使用一个类之前,JVM会将该类的字节码文件(.Class)从磁盘,网络或者其他的来源加载到内存中,并对字节码进行解析,生成Class对象。在Class类中有提供forName()方法,此方法根据ClassPath所配置的路径进行类的加载,如果你的类来源是网络,文件。那么这个时候我们就要手动实现类加载器。

        首先我们要了解类加载器是什么:在JVM的类加载阶段中有一个动作叫做“通过一个类的全限定名来描述此类的二进制字节流”。这个动作被放在JVM的外部实现,以便让应用程序自己决定去如何获取所需要的类。实现这个动作的代码块就叫做“类加载器”

        现在我们来介绍一下ClassLoader。首先用一副图来介绍一下它。


        这张图中有四个要介绍的加载器,我现在来逐一介绍

        1:Bootstrap(启动类加载器):只有这个类加载器是在JVM的内部的,这个加载器使用C++实现。除了这个类加载器以外,其他的类加载器都是Java实现的并且存在于JVM的外部,并且都是java.lang.ClassLoader类的子类。这个加载器的作用是加载<Java_Runtime_Home>/lib目录中的文件,并且只加载特定文件名的文件,就是说如果你才这个目录下放了一个别的.jar文件,此加载器都是不会加载它的。除此之外,因为这个加载器是JVM的一部分,所以该加载器无法被Java程序使用

        2:ExtClassLoader(扩展类加载器):负责加载<Java_Runtime_Home>/lib/ext目录下或者被java.ext.dirs系统变量指定路径下的类库,此加载器可以被开发者直接使用。

        3:AppClassLoader(应用程序类加载器):负责加载用户类路径中的文件,如果用户没有自定义类加载器,那么此加载器就会是程序中的默认类加载器

类加载器中的双亲委派模型

        双亲委派模型可以保证Java程序的稳定执行,首先看一下类加载器之间的关系,用一幅图来表示。


扫描二维码关注公众号,回复: 759785 查看本文章

关于类的加载与双亲委派模型,有四点需要我们总结

        1:类的加载过程由代理设计模式实现

        2:这四种加载器的层次关系就叫做双亲委派模型

        3:除了最顶层的Bootstrap加载器之外,其余的加载器都要有自己的父类,要注意的是,这里的父类加载器不是通过继承来实现的,而是采用组合的方式实现。

        4:当一个加载器收到加载请求时,先不自己处理,而是把加载请求委托给父加载器处理,每一层的加载器都是如此。所以只有当BootStrap加载器都没有办法处理加载请求的时候,子加载器才会尝试自己去加载。这就是双亲委派模型的工作流程。例如java.lang.Object类,它存放在rt.jar中,就是说无论我们用哪一个加载器都会加载这个类。所以我们会得到一个结论:Object类在各种类加载器的环境里都是同一个类。

        双亲委派模型从JDK1.2之后引入,但是不强制要求,可以破坏此机制来加载类。最典型的案例就是OSGI(Java模块化技术)也叫做热加载,大致的意思就是:在JVM进程运行的过程中,如果新的类加入,不用重启JVM也可以加载那个类。

ClassLoader进行实现双亲委派机制

        首先我们要看一下ClassLoader类中的loadClass方法源码

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,检查类是否已经被加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 如果没有找到类,就抛出ClassNotFoundException异常
                    // 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);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

        首先ExtClassLoader和AppClassLoader都继承于ClassLoader类,根据loadClass方法的源码来总结一下,整个类的加载过程可以分成如下的几个步骤。

        1:首先查看要加载的类是否已经被加载过了。

        2:如果还没有被加载,则判断当前类加载器的父加载器是否为空,不为空则委托给父类加载器去加载,如果为空的话(Bootstrap加载器在Java程序中不可见,所以为null),就调用Bootstrap(启动类加载器)去加载。

        3:如果在第二步加载失败了,就调用自定义加载器去进行加载。

自定义加载器

        其实我们在大多数情况下都使用系统的加载器进行类的加载,但是在有些特定的情况下我们不得不使用自定义的类加载器:假设我们现在从网络上获取了一个类放在桌面上,这个时候系统类的加载器就没有办法对其进行加载。所以在这个时候我们就需要来自定义加载器,自定义加载器一般都是继承于ClassLoader类并且覆写findClass方法即可。以下我通过一个例子来说明自定义加载器的流程。

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;



class MyClassLoader extends ClassLoader{//自定义加载器
    public Class<?> LoadData(String classname)throws Exception{//输入要加载的类的名称
        byte[] classdata = this.loadClassData();
        return super.defineClass(classname,classdata,0,classdata.length);
    }

    private byte[] loadClassData() throws Exception{//通过指定的路径进行文件加载,也就是二进制文件读取
        InputStream input = new FileInputStream("C:\\Users\\Lenovo\\Desktop\\student1.class");//桌面上的.class文件路径
        //拿到所有字节内容,放到内存中
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        //读取输出缓存区
        byte[] data = new byte[50];
        int temp = 0;
        while((temp = input.read(data))!=-1){
            byteArrayOutputStream.write(data,0,temp);
        }
        byte[] result = byteArrayOutputStream.toByteArray();
        input.close();
        byteArrayOutputStream.close();
        return result;
    }

}

public class Main{
    public static void main(String[] args)throws Exception{
        Class<?> cls = new MyClassLoader().LoadData("student1");
        System.out.println(cls.getClassLoader());
        System.out.println(cls.getClassLoader().getParent());
        System.out.println(cls.getClassLoader().getParent().getParent());

    }
}


        我们会发现放在桌面上的.class文件被我自定义的类加载器所加载。说明了自定义类加载器可以对动态的类路径进行加载操作。此外,最好不要覆写loadClass方法,这样会破坏双亲委托模式

        最后一点:当比价两个类对象是否相等的时候,必须要有同一个类加载器加载的时候才有意义。否则,即使两个类来自于同一个.class文件,只要类加载器不相同,那么这两个类对象注定不会相等。


猜你喜欢

转载自blog.csdn.net/qq_38449518/article/details/80232574