概述

  虚拟机加载Class文件(二进制字节流)到内存,并对数据进行校验、转换解析和初始化,最终形成可被虚拟机直接使用的Java类型,这一系列过程就是类的加载机制。

类的加载时机

  类从被虚拟机加载到内存开始,直到卸载出内存为止,整个生命周期包括:加载——验证——准备——解析——初始化——使用——卸载 这7个阶段。其中验证、准备、解析3个部分统称为连接。

  

 哪些情况能触发类的初始化阶段?(前提:加载、验证、准备自然是已经执行完了)

  1. 遇到new、getstatic、putstatic、invokestatic 这4条指令时如果类没有初始化则会触发其初始化,(工作中触发这4种指令最常见的场景:new实例化对象、读取or设置类的静态字段【final修饰或者已经把静态字段放入常量池的除外】、调用类的静态方法)
  2. 使用反射的时候
  3. 初始化类的时候如果其父类还没进行初始化,则需要先触发父类的初始化
  4. 虚拟机启动时,需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个类
  5. 使用jdk1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄。切这个句柄对应的类没有初始化,则需要先触发其初始化

   注意:所有引用类的方式都不会触发初始化(被动引用)例如:创建数组、引用final修饰的变量、子类引用父类的静态变量 不会触发子类初始化但是会触发父类初始化

类的加载过程

  加载  

    加载是类加载的一个阶段,在加载阶段  虚拟机需要完成下面3件事情

      1.通过类的全限定名来获取定义此类的二进制字节流 

      2.将这个字节流所代表的静态存储结构转化成方法区的运行时数据结构

      3.在内存中生成一个代表此类的java.lang.Object对象,作为方法区这个类的各种数据的访问入口

    相对于类加载的其他阶段,加载阶段(准确的说,是加载阶段中获取类的二进制字节流的动作)是开发人员可控性最强的。因为加载阶段既可以使用系统提供的引导类加载器来完成,也可以由开发人员自定义的类加载器来完成(即重写类加载器的loadClass()方法)。

    加载完成后,外部的二进制字节流就转化成虚拟机所需的格式存储在方法区中,然后在内存中实例化一个java.lang.Class类的对象。这个对象将作为程序访问方法区中的这些类型数据的外部接口。

 加载阶段与连接阶段的部分内容是交叉进行的,并不是加载完成后才能执行验证等操作。这些夹在加载之中的动作仍然属于连接阶段的内容,这两个阶段的开始时间仍然保持着固定的先后顺序。

  验证   

    验证是连接的第一步,为了保证加载的二进制字节流所包含的信息是符合虚拟机规范的。

       验证阶段大致分为下面4个检验动作:

        文件格式验证:验证字节流是否符合Class文件格式规范。例如:是否以魔数 0xCAFEBABE 开头、主次版本号是否在当前虚拟机处理范围内、常量池中的常量是否有不被支持的类型。

        元数据验证:对字节码描述的信息进行语义分析。例如: 这个类是否有父类、是否正确的继承了父类。

        字节码验证:通过数据流和控制流的分析,确定程序语义是合法的、符合逻辑的(说白了就是对类的方法体进行分析确保方法在运行时不会危害虚拟机)。

        符号引用验证:确保解析动作能正常执行。

    验证阶段是非常重要,但不一定是必要的阶段(因为对程序运行期没有影响)。如果所运行的全部代码都已经被反复使用和验证过,那么在实施阶段可以使用-Xverify:none参数来关闭验证。

  准备

    为类变量分配内存,并将其初始化为默认值。(此时为默认值,在初始化的时候才会给变量赋值)即在方法区中分配这些变量所使用的内存空间

  解析 

    解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。

      符号引用:以一组符号来描述引用的目标,符号可以是任何形式的字面量。

      直接引用:指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。

  初始化

    为变量的静态变量赋予正确的初始值;

类加载器的种类

  启动类加载器(Bootstrap ClassLoader)

      负责加载JRE的核心类库;

  扩展类加载器(Extension ClassLoader)

      负责加载JRE扩展目录ext中jar类包;

  系统类加载器(Application ClassLoader)

      负责加载ClassPath路径下的类包;

  用户自定义加载器(User ClassLoader)

      负责加载用户自定义路径下的类包;

类加载机制

    

  全盘负责委托机制

    当一个ClassLoader加载一个类的时候,除非显示的使用另一个ClassLoader,该类所依赖和引用的类也是由这个ClassLoader载入

  双亲委派机制

    指先委托父类加载器寻找目标类,在找不到的情况下,在自己的路径中查找并载入目标类

     优势:

        沙箱安全机制;

        避免类的重复加载;

      如何破坏双亲委派机制:

        1.手动调用系统类加载器

          Thread.currentThread().getContextClassLoader();

        2.重写ClassLoad类中的loadClass方法,指定加载那个类;

        3.重写findClass;

 posted on 2019-12-04 20:39  wnwn  阅读(231)  评论(0编辑  收藏  举报