JVM ,类的加载过程中,init 和 clinit 区别

1、init 和 clinit 区别

1)执行时机不同

init 是对象构造器方法,程序执行 new 一个对象调用该对象类的 constructor 方法时才会执行 init 方法,

clinit 是类构造器方法(类的加载过程), jvm 进行类的加载 —–> 验证 —-> 解析 —–> 初始化 ,其中,初始化阶段,jvm会调用 clinit 方法。

2)执行目的不同

init 是 instance 实例构造器,对非静态变量解析初始化;

clinit 是 class 类构造器对静态变量,静态代码块进行初始化。

2、clinit 说明

在准备阶段,变量已经赋过一次系统要求的初始值(注意:如果这个类变量是 static final 的,那么在准备阶段就会根据程序员的意愿完成初始化,而非系统要求的初始值),

而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器 clinit 方法的过程

编译器自动收集类中的所有 类变量 的赋值动作 和 静态语句块(static{}块)中的语句合并产生的,
编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中,只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块 可以赋值,但是不能访问

虚拟机会保证在子类的 clinit 方法执行之前,父类的 clinit 方法已经执行完毕。
因此在虚拟机中第一个被执行的 clinit 方法的类肯定是java.lang.Object。
由于父类的 clinit方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。

接口的 clinit :

接口中不能使用 静态语句块 ,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成 clinit 方法。
与类不同的是,接口的 clinit方法不需要先执行父接口的 clinit方法,只有当父接口中定义的变量使用时,父接口才会初始化。
接口的实现类在初始化时也一样不会执行接口的 clinit方法,只有使用了接口的类变量时才会执行接口的 clinit方法。

类的初始化顺序:

总结:包含父子类和接口类

普通类:

静态变量
静态代码块
普通变量
普通代码块
构造函数


继承的子类:

父类静态变量
父类静态代码块
子类静态变量
子类静态代码块
父类普通变量
父类普通代码块
父类构造函数
子类普通变量
子类普通代码块
子类构造函数


抽象的实现子类: 接口 - 抽象类 - 实现类

接口静态变量
抽象类静态变量
抽象类静态代码块
实现类静态变量
实现类静态代码块
抽象类普通变量
抽象类普通代码块
抽象类构造函数
实现类普通变量
实现类普通代码块
实现类构造函数

3、示例

JVM的类加载机制中,准备阶段和初始化阶段尤为重要,在程序设计中有时候起到至关重要的作用。因此今天来根据一个例子来讲解JVM中<init>和<clinit>的过程。

public class JavaTest {

    public static void main(String[] args) {
        f1();
    }

    static JavaTest javaTest = new JavaTest();

    static {
        System.out.println("1");
    }

    {
        System.out.println("2");
    }

    JavaTest() {
        System.out.println("3");
        System.out.println("a=" + a + ", b=" + b);
    }

    public static void f1() {
        System.out.println("4");
    }

    int a = 100;
    static int b = 200;
}

运行结果是什么?

3.1、解析:

JavaTest 程序的入口是 public static void main , 那么在调用这个 main函数之前,需要执行类的加载过程,类加载成功后才会去调用main方法。

那么,

第一步: 在类加载过程的准备阶段,先对b进行系统的赋值,b = 0

第二步: 在类加载过程的初始化阶段,执行<clinit>方法,那么先执行类变量的初始化,即:static JavaTest javaTest = new JavaTest();

第三步: 在第二步的时候,<clinit>正在执行,而且此时进行类 对象的初始化(new JavaTest(),会去调用<init>方法,

​ 因此会首先执行非静态代码块:System.out.println("2")

​ 然后执行非静态变量的初始化:a = 100 (此时的先后顺序依照代码编写的先后顺序),

​ 然后执行构造函数:System.out.println("3"); System.out.println("a=" + a + ", b=" + b); , 此时a的值为100,b的值还是0,因为<clinit> 还只执行到 static JavaTest javaTest = new JavaTest();

第四步: <init> 方法已经执行完了,那么就接下来执行<clinit> 剩余的部分,

​ 先执行类的静态代码块:System.out.println("2")

​ 再执行类的静态变量初始化:static int b = 200 。(此时的先后顺序依照代码编写的先后顺序),

​ 此时,<clinit>方法就执行完成了。

第五步: <clinit><init> 方法 都已经执行完成了,类已经加载完成,此时就是函数的调用了,JavaTest 的函数入口是 main() 方法,因此会调用静态方法f1():System.out.println("4");到此,整个程序就执行完成了。

3.2、运行结果:

2
3
a=100, b=0
1
4

4、关联文章

JVM之类加载的过程(类加载子系统)

猜你喜欢

转载自blog.csdn.net/xiaojin21cen/article/details/107442238
今日推荐