JVM类生命周期与类的加载过程

JVM类生命周期与类的加载过程

类的生命周期:

在这里插入图片描述

.class文件被加载到虚拟机内存后才可生效。

类加载过程严格按照上述顺序“开始”,但不是按照上述顺序“进行”或“完成”,可能会交错。

1、关于类初始化时机

当且仅当对一个类进行主动引用的时候才会触发初始化阶段。共有5种主动引用场景,详见文末参考文章。

其余被动引用不触发类的初始化,如:

1) 通过子类引用父类的静态字段,不会导致子类的初始化。

2)通过数组定义来引用类,不会触发该类的初始化。如:

public class NotInitialization{
    public static void main(String[] args){
        SClass[] sca = new SClass[10];
    }
}

虚拟机并没有初始化Sclass, 而是触发了一个[Lcn.edu.tju.rico.SClass类的初始化,其中"["代表数组,该类直接继承于Object,创建动作由字节码指令newarray触发。

3)常量在编译阶段会存入调用类的常量池中,不会触发定义该常量的类的初始化。如:

// 定义类
public class ConstClass{

    static{
        System.out.println("ConstClass init!");
    }

    public static  final String CONSTANT = "hello world";
}
// 调用类
public class NotInitialization{
    public static void main(String[] args){
        System.out.println(ConstClass.CONSTANT);
    }
}/* Output: 
        hello world
 *///:~

2、进一步解释生命周期的每一步

1)加载

参加文末参考文章

2)验证

cafebabe,参见文末参考文章

3)准备阶段

对类的static变量的内存分配(jdk1.8: 元空间直接内存中), 与第一次初始化(零值)。

注意:若是final 的static变量,则初始化为定义时所赋的值。

4)解析

参加文末参考文章

5)初始化阶段

第二次对类变量初始化,这一次是按照程序员的意志。

执行类构造器<clinit>()方法,整合,合并所有类变量的赋值动作静态语句块,类似于对象创建(类的实例化)中的实例构造器<init>()

注意:静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。如下:

public class Test{
    static{
        i=0;
        System.out.println(i);//Error:Cannot reference a field before it is defined(非法向前应用)
    }
    static int i=1;
}

那么注释报错的那行代码,改成下面情形,程序就可以编译通过并可以正常运行了:

public class Test{
    static{
        i=0;
        //System.out.println(i);
    }

    static int i=1;

    public static void main(String args[]){
        System.out.println(i);
    }
}/* Output: 
        1
 *///:~

虚拟机会保证在子类类构造器<clinit>()执行之前,父类的类构造<clinit>()执行完毕。

虚拟机会保证一个类的类构造器在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的类构造器,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。且,在同一个类加载器下,一个类型只会被初始化一次

3、小结

结合java对象的创建,我们可以归纳,创建一个java对象需要经历如下几个阶段:

父类的类构造器() -> 子类的类构造器() -> 父类的成员变量和实例代码块 -> 父类的构造函数 -> 子类的成员变量和实例代码块 -> 子类的构造函数。

4、案例分析(类的初始化与实例化的纠结)

public class StaticTest {
    public static void main(String[] args) {
        staticFunction();
    }

    static StaticTest st = new StaticTest();

    static {   //静态代码块
        System.out.println("1");
    }

    {       // 实例代码块
        System.out.println("2");
    }

    StaticTest() {    // 构造函数
        System.out.println("3");
        System.out.println("a=" + a + ",b=" + b);
    }

    public static void staticFunction() {   // 静态方法
        System.out.println("4");
    }

    int a = 110;    // 实例变量/成员变量
    static int b = 112;     // 静态变量
}
/* Output: 
        2
        3
        a=110,b=0 // 因为先进行了实例化,所以这里b是“准备”阶段的0值。
        1
        4
 *///:~

我们看一下上述代码的执行过程:

  1. 首先第3行调用StaticTest类的静态方法,触发了该类的初始化
  2. 类的初始化执行的第一步就是第6行语句。
  3. 由于第6行是==类的实例化,也就是说,这里将类的实例话嵌入到了类的加载过程之中,在准备阶段之后,类的初始化之前。==类似这样:
public class StaticTest {
    <clinit>(){
        a = 110;    // 实例变量
        System.out.println("2");        // 实例代码块
        System.out.println("3");     // 构造函数中代码的执行
        System.out.println("a=" + a + ",b=" + b);  // 构造函数中代码的执行
        类变量st被初始化
        System.out.println("1");        //静态代码块
        类变量b被初始化为112
    }
}

所以就有了上述的输出。

结论:实例初始化不一定要在类初始化结束之后才开始初始化。

参考文章:https://blog.csdn.net/justloveyou_/article/details/72466105

发布了28 篇原创文章 · 获赞 16 · 访问量 3187

猜你喜欢

转载自blog.csdn.net/Newbie_J/article/details/103329088
今日推荐