zoukankan      html  css  js  c++  java
  • 【学习笔记】深入理解Java虚拟机 第七章 虚拟机类加载机制

    类加载机制

    虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

    JVM类加载分为5个过程:加载,验证,准备,解析,初始化,使用,卸载

    加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)

    加载

    加载主要是将.class文件(并不一定是.class。可以是ZIP包,网络中获取)中的二进制字节流读入到JVM中。
    在加载阶段,JVM需要完成3件事:
    1)通过类的全限定名获取该类的二进制字节流;
    2)将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
    3)在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

    连接

    验证

    验证是连接阶段的第一步,主要确保加载进来的字节流符合JVM规范。
    验证阶段会完成以下4个阶段的检验动作:
    1)文件格式验证
    2)元数据验证(是否符合Java语言规范)
    3)字节码验证(确定程序语义合法,符合逻辑)
    4)符号引用验证(确保下一步的解析能正常执行,发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段——解析阶段中发生。符号引用验证可以看做是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验)

    准备

    准备是连接阶段的第二步,主要为静态变量在方法区分配内存,并设置默认初始值。

    解析

    解析是连接阶段的第三步,是虚拟机将常量池内的符号引用替换为直接引用的过程。

    初始化

    初始化阶段是类加载过程的最后一步,主要是根据程序中的赋值语句主动为类变量赋值。
    注:
    1)当有父类且父类未初始化的时候,先去初始化父类;
    2)再进行子类初始化语句。

    什么时候需要对类进行初始化?

    有且只有:

    1)遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
    2)使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
    3)当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
    4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
    5)当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

    通过子类引用父类的静态字段,不会导致子类初始化

    通过数组定义来引用类,不会触发此类的初始化

    常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。

    类加载器

    类加载器实现的功能是即为加载阶段获取二进制字节流的时候。

    JVM提供了以下3种系统的类加载器:

    • 启动类加载器(Bootstrap ClassLoader):最顶层的类加载器,负责加载 JAVA_HOMElib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。
    • 扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOMElibext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
    • 应用程序类加载器(Application ClassLoader):也叫做系统类加载器,可以通过getSystemClassLoader()获取,负责加载用户路径(classpath)上的类库。如果没有自定义类加载器,一般这个就是默认的类加载器。

    类加载器之间的层次关系如下:

    类加载器之间的这种层次关系叫做双亲委派模型。
    双亲委派模型要求除了顶层的启动类加载器(Bootstrap ClassLoader)外,其余的类加载器都应当有自己的父类加载器。这里的类加载器之间的父子关系一般不是以继承关系实现的,而是用组合实现的。

    双亲委派模型的工作过程

    如果一个类接受到类加载请求,他自己不会去加载这个请求,而是将这个类加载请求委派给父类加载器,这样一层一层传送,直到到达启动类加载器(Bootstrap ClassLoader)。
    只有当父类加载器无法加载这个请求时,子加载器才会尝试自己去加载。

    双亲委派模型的代码实现

    双亲委派模型的代码实现集中在java.lang.ClassLoader的loadClass()方法当中。
    1)首先检查类是否被加载,没有则调用父类加载器的loadClass()方法;
    2)若父类加载器为空,则默认使用启动类加载器作为父加载器;
    3)若父类加载失败,抛出ClassNotFoundException 异常后,再调用自己的findClass() 方法。

    loadClass源代码如下:

    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        //1 首先检查类是否被加载
        Class c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                 //2 没有则调用父类加载器的loadClass()方法;
                    c = parent.loadClass(name, false);
                } else {
                //3 若父类加载器为空,则默认使用启动类加载器作为父加载器;
                    c = findBootstrapClass0(name);
                }
            } catch (ClassNotFoundException e) {
               //4 若父类加载失败,抛出ClassNotFoundException 异常后
                c = findClass(name);
            }
        }
        if (resolve) {
            //5 再调用自己的findClass() 方法。
            resolveClass(c);
        }
        return c;
    }
    

      

    破坏双亲委派模型

    双亲委派模型很好的解决了各个类加载器加载基础类的统一性问题。即越基础的类由越上层的加载器进行加载。
    若加载的基础类中需要回调用户代码,而这时顶层的类加载器无法识别这些用户代码,怎么办呢?这时就需要破坏双亲委派模型了。

    参考:https://my.oschina.net/u/1458864/blog/2004785

  • 相关阅读:
    htm与html的区别
    CLR笔记:3.共享程序集合强命名程序集
    CLR笔记:5.基元,引用和值类型
    CLR笔记:13.数组
    CLR笔记:18.可空值类型
    正则表达式
    代码大全
    wcf的部署
    Json相关
    $.ready和onload
  • 原文地址:https://www.cnblogs.com/mcq1999/p/12130287.html
Copyright © 2011-2022 走看看