一、JVM探索之类的初始化顺序

0、本文要点:

  • 类的生命周期
  • 类的初始化顺序
  • 对于有继承关系的类的初始化
  • 总结

1、类的生命周期

  首先谈到类的初始化过程,就不得不复习一下类的生命周期(参考资料:https://blog.csdn.net/zhengzhb/article/details/7517213 作者:割韭韭)

 

 

 

  类的生命周期如下:

  (1)加载

    找到需要加载的类并把类的信息加载到jvm的方法区中,然后在堆区中实例化一个java.lang.Class对象,作为方法区中这个类的信息的入口。

    常用的Hotspot是需要用到类时才加载。

  (2)链接:1️⃣验证;2️⃣准备;3️⃣解析

    连接阶段比较复杂,一般会跟加载阶段和初始化阶段交叉进行,这个阶段的主要任务就是做一些加载后的验证工作以及一些初始化前的准备工作,可以细分为三个步骤:验证、准备和解析。

  (3)初始化    

  如果一个类被直接引用,就会触发类的初始化。在java中,直接引用的情况有:

  • 通过new关键字实例化对象、读取或设置类的静态变量、调用类的静态方法。
  • 通过反射方式执行以上三种行为。
  • 初始化子类的时候,会触发父类的初始化。
  • 作为程序入口直接运行时(也就是直接调用main方法)。

  除了以上四种情况,其他使用类的方式叫做被动引用,而被动引用不会触发类的初始化。
  以下的被动引用不会引起类的初始化

  • 引用父类的静态字段,只会引起父类的初始化,而不会引起子类的初始化。
  • 定义类数组,不会引起类的初始化。
  • 引用类的常量,不会引起类的初始化。

在类的初始化阶段,只会初始化与类相关的静态赋值语句和静态语句,也就是有static关键字修饰的信息,而没有static修饰的赋值语句和执行语句在实例化对象的时候才会运行。

  (4)使用

  类的使用:实例化对象  

  (5)卸载

  以下三个条件全部满足,jvm就会在方法区垃圾回收的时候对类进行卸载 

  • 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
  • 加载该类的ClassLoader已经被回收。
  • 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。

2、类的初始化顺序

  (1)解释

  这里我们所说的初始化顺序,其实是一个class文件里各个部分的执行顺序

  (2)结论

  前提:加载类方法(注意是加载)  

  第一步:

    发生在链接-准备阶段的初始化:

    1)为类常量(static final)赋正式值

    2)为类变量赋默认值(注意是系统默认值,不是代码给定的初始值)

 

    注意:类变量、静态代码块的执行顺序按代码顺序从上到下,代码块放在前面会先执行代码块,但此时代码块,可以对下面的类变量赋其他值,但是如果不能对类变量进行读取操作,否则会报 error: illegal forward reference(不合法的向前引用),这个道理也适用在对象变量和普通代码块中间。

  第2步:

    1)发生在初始化阶段的类变量赋初始值

      执行静态代码块

  第3步:

    1)发生在使用阶段,如果new了对象

      1️⃣成员变量、普通代码块初始化

      2️⃣构造器

  直接上代码:

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello, World");
        son.method();//静态方法调用 执行1、2
        //new son(); //对象调用 执行1、2、3、4
    }
}

class son{
    //2、静态块1
    static{
        i = 666;
        System.out.println("静态代码块1");
    }
    //1、静态变量 i\j,常量k赋默认值 
    //2、静态变量 i\j赋值初值值
    static int i;
    static int j = 2;
    static final int k = 3;

    //2、静态块2    
    static{
        System.out.println("静态代码块2");
        j = 444;
        System.out.println("i = " + i + ",j = " + j + ",k = " + k); //i = 666,j = 444,k = 3
    }

    //4、构造器    
    public son(){
        a = 112;
        System.out.print("构造器"  + ",j = " + j);
        System.out.print("构造器"  + ",a = " + a);
    }
    
    //3、成员变量
    int a = 111;

    //3、普通代码块    
    {
        System.out.print("代码块1");
        b = 444;
        System.out.println("a = " + a );
        //System.out.println("a = " + a + ",b" +b); //error: illegal forward reference  指b
    }
    
    int b = 333;
    
    public static void method(){
        System.out.print("静态方法");
        i = 333;
        System.out.println("i = " + i + ",j = " + j + ",k = " + k); //i = 333,j = 444,k = 3
    }
    
}

3、对于有继承关系的类初始化过程:

 

 

 总结一句初始化来说就是:先父后子,先类(静态)后对象,对象构方法在最后。

 

另外附一道面试题,帮助了解类加载

public class Test {
    public static void main(String[] args) {
        A a = A.getInstance();
      /*解题原则:类只加载一次,因此类变量只初始化一次,
      A:
      先执行类的实例化,此时需要类的初始化
      然后类变量 value2 执行一次赋值
      B:
      先执行类的初始化,其中value2已经赋值,然后在执行对象定义,此时不会再初始化value1\value2
      */ System.out.println(
"A value1:" + a.value1);//1 System.out.println("A value2:" + a.value2);//0 B b = B.getInstance(); System.out.println("B value1:" + b.value1);//1 System.out.println("B value2:" + b.value2);//1 } } class A{ private static A a = new A(); public static int value1; public static int value2 = 0; private A(){ value1++; value2++; } public static A getInstance(){ return a; } } class B{ public static int value1; public static int value2 = 0; private static B b = new B(); private B(){ value1++; value2++; } public static B getInstance(){ return b; } }

4、总结:

  1. 对象的创建之前需要经历类的初始化
  2. 一个类的初始化只进行一次
  3. 类的初始化一般包括加载、链接、初始化三个过程
  4. 初始化的对象:
    1. 链接-准备阶段:类常量赋最终值,类变量赋系统默认值
    2. 初始化阶段:静态代码块、类变量赋值,此时类变量赋初始值
  5. 类的初始化开始在对象实例化之前,结束在对象被回收之后
  6. 类的涉及的内存区域:方法区、堆

 

posted @ 2021-09-04 20:09  低手寂寞  阅读(149)  评论(0)    收藏  举报