类的加载过程

概括

  1. 在Java中数据类型分为基本数据类型和引用数据类型。基本数据类型由虚拟机预先定义,引用数据类型需要进行类的加载
  2. 从class文件到加载到内存中的类,到类卸载出内存为止,它的整个生命周期为:加载=>链接(验证、准备、解析)=>初始化=>使用=>卸载

加载阶段

加载完成的操作

  1. 加载:将Java类的字节码文件加载到机器内存中,并在内存中构建出Java类的原型--类模板对象
  2. 加载阶段:查找并加载类的二进制数据,生成Class的实例

二进制流的获取方式

  1. 读入class文件
  2. 读入jar、zip等归档数据包,提取类文件
  3. 事先存放在数据库中的二进制数据
  4. 使用类似于HTTP之类的协议通过网络进行加载
  5. 在运行时生存一段class的二进制信息等

类模型与Class实例的位置

  1. 加载的类在JVM中创建相应的类结构,类结构会存储在方法区(JDK1.8之前:永久代;JDK1.8及之后:元空间)
  2. 类将.class文件加载至元空间后,会在堆中创建一个Java.lang.Class对象,用来封装类位于方法区内的数据结构,该Class对象是在加载类的过程中创建的,每个类都对应有一个class类型的对象

数组类的加载

  1. 创建数组类的情况稍微有些特殊,因为数组类本身并不是由类加载器负责创建,而是由3VM在运行时根据需要而直接创建的,但数组的元素类型仍然需要依靠类加载器去创建。创建数组类的过程:
  2. 如果数组的元素类型是引用类型,那么就遵循定义的加载过程递归加载和创建这个数组的元素类型
  3. JVM使用指定的元素类型和数组维度来创建新的数组类
  4. 如果数组的元素类型是引用类型,数组类的可访问性就由元素类型的可访问性决定。否则数组类的可访问性将被确性定义为public。

链接阶段

验证阶段

Verification ensures that the binary representation of a class or interface is structurally correct . Verification may cause additional classes and interfaces to be loaded but need not cause them to be verified or prepared.
验证是链接阶段的第一步,用以保证加载的字节码是合法、符合规范的

准备阶段

  1. 为类的静态变量分配内存,并将其初始化为默认值
  2. 不包括用 static final 修饰的基本数据类型,因为 final 在编译的时候已经分配了,在准备阶段会显示赋值
  3. 不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中

解析阶段

将类、接口、字段和方法的符号引用转为直接引用

初始化阶段

  1. 为类的变量赋予正确的初始值
  2. 到了初始化阶段,才开始真正执行类中定义的 Java 程序代码
  3. 它是由类中静态成员变量的赋值语句以及static语句块合并而成的
  4. 在加载一个类之前,虚拟机总是会试图加载该类的父类,因此父类的总是在子类cclinit>之前被调用
  5. Java编译器在以下情况并不会为类产生()初始化方法
    5.1 一个类中并没有声明任何的类变量,也没有静态代码块
    5.2 一个类中声明类变量,但是没有明确使用类变量的初始化语句以及静态代码块来执行初始化操作时
    5.3 一个类中包含static final修饰的基本数据类型的字段,这些类字段初始化语句采用编译时常量表达式
  6. 使用static + final修饰,且显示赋值中不涉及到方法或构造器调用的基本数据类型或String类型的显式赋值,是在链接阶段的准备环节进行
  7. ()是带线程安全的,如果在一个类的()方法中耗时很长,可能造成多个线程阻塞,引发死锁

初始化

Java程序对类的使用分为两种:主动使用和被动使用

主动使用

  1. Class只有在首次使用的时候才会被装载,一个类或接口在初次使用(主动使用)前,必须要进行初始化。主动使用即以下几种情况:
    1.1 创建一个类的实例
    1.2 调用类的静态方法,即当使用了字节码 invokestatic 指令
    1.3 使用类或接口的静态字段(final修饰符特殊考虑)
    1.4 使用反射类的方法
    1.5 初始化子类时,如果发现其父类还没有初始化,则需要先触发其父类的初始化
    1.6 如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化,该接口要在其之前被初始化
    1.7 当虚拟机启动时,用户需要指定一个要执行的主类〈包含main()方法的那个类),虚拟机会先初始化这个主类
    1.8 当初次调用MethodHandle实例时,初始化该MethodHandle 指向的方法所在的类。(涉及解析REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄对应的类)
  2. 当Java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口

被动使用

除了主动使用就是被动使用,被动使用不会引起类的初始化

类的使用

类的卸载

  1. 启动类加载器加载的类型在整个运行期间是不可能被卸载的(jvm和jls规范)
  2. 被系统类加载器和扩展类加载器加载的类型在运行期间不太可能被卸载,因为系统类加载器实例或者扩展类的实例基本上在整个运行期间总能直接或者间接的访问的到,其达到unreachable的可能性极小。
  3. 被开发者自定义的类加载器实例加载的类型只有在很简单的上下文环境中才能被卸载,而且一般还要借助于强制调用虚拟机的垃圾收集功能才可以做到。可以预想,稍微复杂点的应用场景中(比如:很多时候用户在开发自定义类加载器实例的时候采用缓存的策略以提高系统性能),被加载的类型在运行期间也是几乎不太可能被卸载的(至少卸载的时间是不确定的)。
posted @ 2021-12-19 18:55  翻蹄亮掌一皮鞋  阅读(43)  评论(0)    收藏  举报