JAVA基础学习【JVM篇】——类加载器

虚拟机设计团队把加载阶段中的“通过一个类的全额限定名来获取此类的二进制字节流”放到虚拟机外部去实现,以便让程序自己去决定如何获取所需要的类。实现这个动作的代码模块被称为“类加载器”

一般来说,Java 虚拟机使用 Java 类的方式如下:
1. JAVA源程序被编译器编译之后形成字二进制节码文件(class文件)
2. 类加载器负责读取该二进制字节码,并转化为java.lang.class的一个实例,这个实例也就是我们所说的class对象

类与类加载器

对于任意一个类,都需要由加载这个类的类加载器和这个类本身来确定这个类在虚拟机中的唯一性,每一个类加载器,都有一个类名称空间(在JAVA基础学习【JVM篇】——类加载机制中介绍数组类的加载时提到过)。通俗来说:比较两个类是否相等,首先得被同一个类加载器加载才有意义。否则,就算是来源于同一个文件,但是类加载器不同,产生的也是不同的类

双亲委派模型

分类

从JAVA虚拟机的角度讲,只存在两种不同的类加载器

  1. 启动类加载器,用C++实现,是虚拟机自身的一部分
  2. 其他的类加载器,使用JAVA语言实现,独立于虚拟机之外,并且全部继承自抽象类java.lang.ClassLoader

从JAVA开发人员的角度来看,还可以分得更加细致,分为以下三种:

  1. 启动类加载器:这个类加载器负责加载放在<JAVA_HOME>\lib目录中的,或者是被-Xbootclasspath参数所指定的路径中的,并且是虚拟机所识别的(按照文件名识别,如rt.jar,名字不符合类库,就算放在目录下,也不会被加载)。启动类加载器不能被JAVA程序直接引用,用户在编写自定义加载器时,如果需要把请求委托给引用类加载器,那么直接使用null替代即可。

    出于安全考虑,启动类加载器只加载包名为java、javax、sun等开头的类。

  2. 扩展类加载器:这个加载器负责加载<JAVA_HOME>/lib/ext目录中的,或者被java.ext.dirs系统变量指定的路径中的所有类库,开发者可直接使用该类加载器。

  3. 应用程序类加载器:这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也成为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可用直接使用这个类加载器,如果应用程序中没有自定义过类加载器,那么这个类加载器就是程序中默认的加载器

双亲委派机制
上图中展示的这种类加载器之间的层次关系,称为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类记载器外,所有的加载器都有自己的父加载器。这里的类加载器的关系不是继承,而是以组合的关系来复用父加载器的代码

工作过程

如果一个类加载器收到了类加载的请求,它首先不会自己尝试加载这个类,而是把这个请求委托给自己的父加载器,每一层次都是如此,因此所有的加载请求都会被传递到启动类加载器中,只有当父加载器反馈自己无法加载此类时,子加载器才会尝试自己加载此类

双亲委派模型的好处

采用双亲委派模型的好处是JAVA随着类加载器有了一定的层次关系。

  • 例如java.lang.Object,他存放在rt.jar中,无论哪一个类加载器记载这个类,最终都是委托启动类加载器加载,因此Object类在程序的各种类加载环境中都是同一个类。 通过这种层次关系可以避免类的重复加载,如果父加载器加载过一遍,那么子加载器就不需要再进行加载。
  • 其次是考虑到安全因素,加入从网络中传来一个java.lang.Integer的类,通过双亲委派机制交给启动类加载器加载,但是启动类加载器之前已经加载过该类了,会直接返回已经加载过的Integer.class,这样做可以防止核心API被篡改。
  • 可能你会想,如果我们在classpath路径下自定义一个名为java.lang.SingleInterge类(该类是胡编的)呢?该类并不存在java.lang中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,将反向委托给子类加载器加载,最终会通过系统类加载器加载该类。但是这样做是不允许,因为java.lang是核心API包,需要访问权限,强制加载将会报出如下异常ClassNotFoundException

双亲委派模型的实现

首先检查类是否被加载过,若没有加载则调用父加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器(只有启动类加载器没有父加载器)。如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
    //check the class has been loaded or not
    Class c = findLoadedClass(name);
    if(c == null) {
        try{
            if(parent != null) {
                c = parent.loadClass(name, false);
            } else{
                c = findBootstrapClassOrNull(name);
            }
        } catch(ClassNotFoundException e) {
            //if throws the exception , the father can not complete the load
        }
        if(c == null) {
            c = findClass(name);
        }
    }
    if(resolve) {
        resolveClass(c);
    }
    return c;
}

猜你喜欢

转载自blog.csdn.net/ran_Max/article/details/80633458