前言:

类加载:是将 class 文件中的二进制数据读取到内存之中,然后将该字节流所代表的静态存储结构转换为 方法区 中运行时的数据结构,并且在堆内存中生成一个该类的 java.lang.Class 对象,作为访问方法区数据结构的 入口

image.png

类加载过程分析:

加载阶段 连接阶段 初始化阶段(延迟机制
查找并加载二进制文件,即 class 文件 验证:确保类文件的正确性 为类的静态变量赋予正确的初始值(代码编写阶段)
延迟机制:类在首次使用的时候才会被初始化
准备:为类的静态变量分配内存,并且为其初始化默认值
解析:符号引用、字段、类方法、接口方法等的解析

1.连接阶段的解析过程
……
2.初始化阶段
初始化过程主要是执行 <clinit>() 方法的过程。clinit 是 class initialize 的简写,是在 编译阶段 生成的,包含了所有 类变量的赋值动作静态代码块 的执行代码。执行顺序由执行语句在源文件中的出现顺序(指令重排序)所决定。JVM保证了() 方法在多线程的执行环境下的同步语义,因此是线程安全的。
【注意】() 方法与类的构造方法不同,他不需要显式的调用父类的构造器,JVM会保证父类的 () 方法最先执行。因此,父类的静态变量总是能够得到优先赋值

  1. public class ClassInit {
  2. static class Parent {
  3. /** 1.对 value 赋值 10 */
  4. static int value = 10;
  5. static {
  6. /** 2.对 value 进行二次赋值 20 */
  7. value = 20;
  8. }
  9. }
  10. static class Child extends Parent {
  11. static int i = value;
  12. }
  13. public static void main(String[] args) {
  14. /** 输出 20,因为父类的<clinit>()方法总是最先执行 */
  15. System.out.println(Child.i);
  16. }
  17. }

类的主动/被动使用:

类在首次使用的时候才会被初始化。使用分为两种:主动使用被动使用主动使用 才会导致的类的初始化;被动使用 不会导致类的加载与初始化。

1.主动使用的六种场景:

  • new 关键字,直接初始化类对象
  • 访问该类中的静态变量时
  • 访问该类中的静态方法时
  • 对该类进行反射操作时
  • 初始化子类,会导致父类的初始化
  • 执行 main 函数,其所在的类会发生初始化

2.被动使用的两个场景:

  • 构造某个类的数组时
  • 引用类的静态常量

    实例分析:

    1. public class Singleton {
    2. private static int a = 0;
    3. private static int b;
    4. private static Singleton instance = new Singleton();
    5. private Singleton() {
    6. a ++;
    7. b ++;
    8. }
    9. public static Singleton getInstance() {
    10. return instance;
    11. }
    12. public static void main(String[] args) {
    13. Singleton instance = Singleton.getInstance();
    14. System.out.println("a=" + instance.a);
    15. System.out.println("b=" + instance.b);
    16. }
    17. }

    image.png
    注:对象实例化的时候,会调用类中的构造方法,因此 new Singleton() 语句位置的不同会导致 a、b 结果值的不同。
    image.png