JVM类加载机制

类的生命周期

加载,验证,准备,初始化,卸载这五个过程顺序是固定的

类加载时机

  • new、getstatic、putstatic、invokestatic这4个字节码指令时对类进行初始化(即:实例化对象、读写静态对象、调用静态方法时,进行类的初始化)
  • 使用反射机制对类进行调用时,进行类的初始化
  • 初始化一个类,其父类没有初始化时,先初始化其父类
  • 虚拟机启动时,初始化一个执行主类
  • 使用JDK1.7的动态语言支持时,如果MethodHandle实例的解析结果为REF_getstatic、REF_putstatic、REF_invokestatic的方法句柄(即:读写静态对象或者调用静态方法),则初始化该句柄对应类

类加载过程

  • ①加载

    在加载阶段,虚拟机需要完成以下三件事情

    • 通过一个类的全限定名来获取此类的二进制字节流
    • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
    • 在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区的这些数据的访问入口
  • ②验证

    • 文件格式验证
    • 元数据验证
    • 字节码验证
    • 符号引用验证
  • ③准备

    • 类变量初始化
    • 类变量赋值
      • final 准备阶段赋值
      • 非final,初始化时赋值

    方法区

  • ④解析

解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程

虚拟机规范中并未规定解析阶段发生的具体时间

    • 类或接口
    • 字段
    • 类方法
    • 接口方法
  • ⑤初始化

到了初始化阶段,才真正开始执行类中定义的java程序代码(或者说是字节码)

从另一个角度来说,初始化阶段是执行()方法的过程

- <clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的。
- <clinit>()方法与类的构造函数(或者说实例构造器<init>()方法)不同,它不需要显式地带哦用父类构造器
    > 虚拟机中第一个被执行的<clinit>()方法的类肯定是java.lang.Object.

- 由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。
- <clinit>()方法对于类或接口来说并不是必须的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>()方法
- 接口中不能使用静态语句块,但接口与类不同的是,执行接口的<clinit>()方法不需要先执行父接口的<client>()方法。只有当父接口中定义的变量被使用才初始化。
- 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁和同步。

类加载器

通过一个类的全限定名来获取此类的二进制字节流

类加载器会影响类的相等
  • equals()
  • isAssignableFrom()
  • isInstance()
  • instanceof

双亲委派模型

双亲委派模型工作过程

如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。

破坏双亲委派模型

java中所有涉及SPI的加载动作基本上都破坏了双亲委派模型,例如JNDI,JDBC,JDBC,JCE,JAXB和JBI等。

原文地址:https://www.cnblogs.com/lifan1998/p/10359446.html