clinit()相关解读

251 阅读4分钟

clinit() 方法和 init()方法的区别

  • init is the (or one of the) constructor(s) for the instance, and non-static field initialization.
  • clinit are the static initialization blocks for the class, and static field initialization.

翻译过来就是说

  • init()是instance实例构造器,对非静态变量解析初始化,
  • clinit()是class类构造器对静态变量,静态代码块进行初始化。

clinit()

  1. 初始化阶段就是执行类构造器方法<clinit>()的过程
  2. 此方法不需定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。也就是说,当我们代码中包含static变量的时候,就会有clinit方法
  3. <clinit>()方法中的指令按语句在源文件中出现的顺序执行
  4. <clinit>()不同于类的构造器。(构造器是虚拟机视角下的<init>()
  5. 若该类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕
  6. 虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁

Java 编译器不会生成clinit()方法的场景

  • 场景1:对应非静态的字段,不管是否进行了显式赋值,都不会生成clinit()方法
  • 场景2:静态的字段,没有显式的赋值,不会生成clinit()方法
  • 场景3:比如对于声明为 static final 的基本数据类型的字段,不管是否进行了显式赋值,都不会生成clinit()方法

image.png

image.png

package com.classLoader;

/**
 * @author yatao.xu
 * @version 1.0.0
 * @date 2022-07-20
 **/
public class ClinitTest {
    /**
     * 哪些场景下,Java 编译器就不会生成<clinit>()方法
     */
    //场景1:对应非静态的字段,不管是否进行了显式赋值,都不会生成<clinit>()方法
    public int num = 1;
    //场景2:静态的字段,没有显式的赋值,不会生成<clinit>()方法
    public static int num1;
    //场景3:比如对于声明为 static final 的基本数据类型的字段,不管是否进行了显式赋值,都不会生成<clinit>()方法
    public static final int num2 = 1;

    //存在static成员变量或静态代码块就会有clinit()方法生成
    public static int num3 = 1;

    static {
        System.out.println("加载我了");
    }
    
    public static void main(String[] args) {
        ClinitTest clinitTest = new ClinitTest();
    }
}

结论:

在链接阶段的准备环节赋值的情况:
  • 对于基本数据类型的字段来说,如果使用 static final 修饰,则显式赋值(直接赋值常量,而非调用方法)通常是在链接阶段的准备环节进行
  • 对于 String 来说,如果使用字面量的方式赋值,使用 static final 修饰的话,则显式赋值通常是在链接阶段的准备环节进行
在初始化阶段()中赋值的情况
  • 排除上述的在准备环节赋值的情况之外的情况
最终结论:
  • 使用 static + final 修饰,且显式赋值中不涉及到方法或构造器调用的基本数据类型或String类型的显式赋值,是在链接阶段的准备环节进行
  • 使用static修饰的静态变量在初始化阶段clinit()赋值
package com.classLoader;

/**
 * @author yatao.xu
 * @version 1.0.0
 * @date 2022-07-20
 **/
public class ClassInitTest {
    public int aa = 2;   //
    public Integer aa2 = 2;   //
    public Integer AA3 = Integer.valueOf(1000); //

    private static int num = 1; //在初始化阶段clinit()中赋值(静态变量)

    public static final String s0 = "helloworld0"; //在链接阶段的准备环节赋值 (常量)

    // 也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。
    public static final String FINAL_STR = "aaa";//在链接阶段的准备环节赋值(常量)

    public static final int INT_CONSTANT = 10;//在链接阶段的准备环节赋值(常量)

    public static final Integer INTEGER_CONSTANT1 = Integer.valueOf(100); //在初始化阶段clinit()中赋值(常量+需要计算赋值)

    public static Integer INTEGER_CONSTANT2 = Integer.valueOf(1000); //在初始化阶段clinit()中赋值(静态变量)

    public static final String s1 = new String("helloworld1"); //在初始化阶段clinit()中赋值 (常量+需要计算赋值)

    static {
        num = 2;
        number = 20;
        System.out.println(num);
        System.out.println("加载我了");
        //System.out.println(number);//报错:Illegal forward reference 非法的前向引用。
    }

    //只要静态代码块不使用number,这里是可以在后面定义的
    //在链接的准备阶段: number = 0 -->到了初始化阶段: 先20 --> 后10
    private static int number = 10;

    public static void main(String[] args) {
        System.out.println(ClassInitTest.num);//2
        System.out.println(ClassInitTest.number);//10
        System.out.println(ClassInitTest.FINAL_STR);//aaa
    }
}

image.png