二、类加载器、双亲委派机制

之前一些知识点补充

  • 作用:加载类。

  • 什么时候会加载类呢:new Student(); 在对一个类创建一个新的对象的时候就会加载。通过ClassLoader加载之后,该类会进入栈中,而相应的对象则会进入堆。


package jvm;

public class Car {
    public static void main(String[] args) {
        // 拿到Car的整个类
        Class<Car> carClass = Car.class;
        System.out.println(carClass);
        // -------------------------------------------------------------------------------------------------
        System.out.println("==============================");
        Car car1 = new Car();
        Car car2 = new Car();
        Car car3 = new Car();
        Car car4 = new Car();

        System.out.println(car1.hashCode());
        System.out.println(car2.hashCode());
        System.out.println(car3.hashCode());
        System.out.println(car4.hashCode());
        /*
        1163157884
        1956725890
        356573597
        1735600054
         */

        Class<? extends Car> aClass = car1.getClass();
        Class<? extends Car> aClass1 = car2.getClass();

        System.out.println(aClass.hashCode());
        System.out.println(aClass1.hashCode());
        /*
        460141958
        460141958
         */
    }
}

用代码来演示一下:

  • 可以看出同一个类创建出来的不同对象的地址都是不同的。
    • 不同的对象都放在了堆中,里面存储了不同的属性值,然后在栈里面存放了一个指针,使用的时候在从栈里面获得该指针,然后指向堆里面获得相应的数据。使用完就从栈里面弹出,这也是为何栈里面不会有垃圾,而只有堆里面才会有垃圾。
  • 不同的对象,在使用getClass语句的时候获得的类模板反而是一致的。注意这里只是类模板,而不是类加载器,如果想要获得类加载器的话需要获得相应的类模板然后使用getClassLoader方法。

类加载器

  • 作用:加载class文件。

  • 加载:指的是将类的class文件读入进内存中,并且为止创建一个java.lang.class对象,也就是说,当程序使用任何类的时候,都会为之创建一个java.lang.class对象。

  • 链接:连接阶段负责把类的二进制数据合并到JRE中。合并的过程又分为了三个阶段:

    • 验证:用于验证被加载的类是否正确,和其他的类协调一致。保证安全。比如说有数组验证检查等等。
      • 文件格式验证:验证字节流是否符合规范。
      • 元数据验证:堆字节码描述的信息进行语义分析。
      • 字节码验证:最重要的验证环节,分析数据流和控制。确定语义是合法的。
      • 符号引用验证:针对符号转换。
    • 准备:为类的静态变量分配内存和初始值。
    • 解析:将类的二进制数据中的符号引用替换成直接引用。说明一下:符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。
  • 初始化:为类的静态变量赋予正确的初始值。

双亲委派机制

双亲委派机制就是寻找相应Java类的一个具体的方式。其作用就是为了保证安全。

Java类加载器的种类

  • 虚拟机自带的加载器
  • 启动类(根)加载器 Bootstrap classLoader:加载Java的核心类库,该类库在java.lang包下,同时也构造了ExtClassLoader和AppClassloader
  • 扩展类加载器 ExtClassLoader:加载核心类库的一些扩展jar包,一般在jre/lib/ext中
  • 应用程序加载器 AppClassLoader:主要负责加载应用程序的主函数类
    这些加载器的调用是有一定的顺序的,而这个顺序也被称作是双亲委派机制。

Java类加载器的顺序 双亲委派机制

上java源码,打开“java.lang”包下的ClassLoader类。然后将代码翻到loadClass方法:

public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    //              -----??-----
    protected 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 {
                        // 直到最上面的Bootstrap类加载器
                        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.
                    c = findClass(name);
                }
            }
            return c;
    }

由此可见其实整个加载过程是一个巡回的过程,首先在加载的时候会寻找AppClassLoader加载器,查看是否会加载过。如果没有加载过那么就依次往上送,一直找,直到BootStrap ClassLoader加载器。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。

也就是说:寻找一个类加载器的流程就是从上至下寻找,如果最高级BootStrap里面存在,那么就调用根加载器加载,然后依次往下。这个就类似于二叉树的中序遍历序列。

为什么说它可以保证安全呢?

  • 当一个类需要加载的时候,如果BootStrapClassLoader中含有该类,那么就会直接加载该加载器中的。如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。
  • 就算是之后你写了一个同样的java.lang.String类,也不会送入到类加载器中加载。这就是能够保证安全的原因所在。
posted @ 2021-02-27 11:46  一个汉服程序员苏木  阅读(71)  评论(0)    收藏  举报