JVM init和clinit的理解

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

吊炸天的Java题目,请问输出结果是什么?

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;
}

先给出正确答案:

2
3
a=100, b=0
1
4

init和clinit区别

①init和clinit方法执行时机不同

init是对象构造器方法,也就是说在程序执行 new 一个对象调用该对象类的 constructor 方法时才会执行init方法,而clinit是类构造器方法,也就是在jvm进行类加载—–验证—-解析—–初始化,中的初始化阶段jvm会调用clinit方法。


②init和clinit方法执行目的不同

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

 

<clinit>

在准备阶段,变量已经赋过一次系统要求的初始值(注意:如果这个类变量是static final的,那么在准备阶段就会根据程序员的意愿完成初始化,而非系统要求的初始值),而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器<clinit>()方法的过程。

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

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

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

 

类的初始化顺序:总结:包含父子类和接口类

普通类:

静态变量、静态代码块(这俩的优先级取决于编码的先后)
普通变量、普通代码块(这俩的优先级取决于编码的先后)
构造函数


继承的子类:

父类静态变量、父类静态代码块(这俩的优先级取决于编码的先后)
子类静态变量、子类静态代码块(这俩的优先级取决于编码的先后)
父类普通变量、父类普通代码块(这俩的优先级取决于编码的先后)
父类构造函数
子类普通变量、子类普通代码块(这俩的优先级取决于编码的先后)
子类构造函数


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

接口静态变量
抽象类静态变量、抽象类静态代码块(这俩的优先级取决于编码的先后)
实现类静态变量、实现类静态代码块(这俩的优先级取决于编码的先后)
抽象类普通变量、抽象类普通代码块(这俩的优先级取决于编码的先后)
抽象类构造函数
实现类普通变量、实现类普通代码块(这俩的优先级取决于编码的先后)
实现类构造函数

 

解题:

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

那么,

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

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

第三步:在第二步的时候,<clinit>正在执行,而且此时进行类对象的初始化,会去调用<init>方法,因此会首先执行非静态代码块:System.out.println("2"),然后执行非静态变量的初始化:a = 100 (此时的先后顺序依照代码编写的先后顺序),然后执行构造函数:System.out.println("3");System.out.println("a=" + a + ", b=" + b);此时a的值为100,b的值还是0,因为<cinit>还只执行到static JavaTest javaTest = new JavaTest();

第四步:<init>方法已经执行完了,那么就接下来执行<cinit>剩余的部分,先执行类的静态代码块:System.out.println("2"),再执行类的静态变量初始化:static int b = 200。(此时的先后顺序依照代码编写的先后顺序)此时<cinit>方法就执行完成了。

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

综上分析,可以得出结果:

2
3
a=100, b=0
1
4

 

  • 13
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值