Java 类初始化(详解)

Java 是一门面向对象的语言,所以除了基本类型以外都是对象,那么这些对象是怎样从 class 文件加载到虚拟机,然后成为内存中一个个对象的呢?本文就来分析下 Java 中的类是如何初始化的?包括类初始化,实例初始化,构造器等。

一、类加载时机

类的生命周期:从被加载到虚拟机内存中开始,到卸载出内存。一共包括如下阶段:加载(Loading),验证(Verification),准备(Preparation),解析(Resolution),初始化(Initialization),使用(Using)和卸载(Unloading),其中验证,准备,解析 3 个阶段统称为连接(Linking)

那么,在什么样的时机下会触发类的生命周期呢?虚拟机规范严格规定了有且只有以下 5 中情况必须立即对类进行初始化(加载,验证,准备自然在初始化之前进行):

  1. 遇到 newgetstaticputstaticinvokestatic这 4 条字节码指令时,如果类未进行过初始化,那么需首先触发类的初始化。分别对应的场景为:1、使用 new 关键字实例化对象时;2、读取类的静态变量时(被 final修饰,已在编译期把结果放入常量池的静态字段除外);3、设置类的静态变量时;4、调用一个类的静态方法时。
  2. 使用反射对类进行调用时。
  3. 初始化一个类时,如果其父类还未初始化,那么会先触发其父类的初始化。
  4. 当虚拟机启动时,被指定为需要执行的那个主类(main() 方法所在的类),虚拟机需要先初始化。
  5. JDK 1.7 动态语言支持,详见《深入理解 Java 虚拟机》。

其中 1、new 关键字实例化对象是调用对象的构造方法,构造方法也是静态方法的一种(《Java 编程思想》);4、main 方法也是静态方法,所以触发类初始化的时机就是:调用类的静态对象。

现在,类初始化的时机我们清楚了,那么类是如何初始化的呢?实例又是如何初始化的?涉及继承时又是如何初始化的?

二、类初始化,实例初始化

要搞懂类初始化的过程,就要搞懂类初始化和实例初始化。

  • 类初始化 <cinit>:就是类第一次加载到内存时进行的过程,也就是我们在上一节讨论的内容。类初始化只进行一次(前提是被同一类加载器加载),后续使用 new 等实例化对象时都不在进行初始化了,所以类初始化只运行一次。初始化的都是属于类(而不是实例)的内容(静态),所以对所有实例共享。
  • 实例初始化 <init>:也就是实例化对象时每次都会进行的过程,初始化属于实例的内容(非静态),没有实例所拥有的实例内容是不共享的,独有的。

2.1 类初始化

前面已经讨论了类进行初始化的时机,下面就来说说类初始化会干些什么。

在类初始化之前的准备阶段,虚拟机会将类变量(static 修饰的变量)分配内存并设置零值。

类初始化阶段,执行类构造器 <cinit>() 方法<cinit> 类初始化方法有如下特点:

  1. 编译器会在将 .java 文件编译成 .class 文件时,收集所有类初始化代码和 static {} 域的代码,收集在一起成为 <cinit>() 方法;
  2. 子类初始化时会首先调用父类的 <cinit>() 方法;
  3. JVM 会保证 <cinit>() 方法的线程安全,保证同一时间只有一个线程执行;

2.2 实例初始化

构造器:保证实例正确的初始化,能被使用。

所以,既然子类继承了父类,那么子类调用构造函数初始化的,就需要调用父类的构造器,不管是显示调用父类构造器还是 JVM 自动调用,这样才能保证子类正确的被构造。

那么,实例初始化的过程到底是如何的呢?

  1. JVM 收集实例初始化变量和 {} 域组合成实例初始化方法 <init>()
  2. 实例初始化时首先执行 <init>() 方法,然后执行构造函数;
  3. 子类通过构造函数构造实例时会首先调用父类的 <init>() 方法和父类的构造函数,如果没有显示调用父类的构造函数,那么 JVM 会自动调用父类的无参构造函数,保证父类构造函数一定被调用,然后再是子类自己的 <init>() 方法和构造函数;
  4. 至此,实例就构造完毕了;

现在,大概应该清楚类和实例如何初始化和被构造了:

  1. 父类类初始化 <cinit>()
  2. 子类类初始化 <cinit>()
  3. 父类 <init>() + 父类构造器;
  4. 子类 <init>() + 子类构造器;

三、示例:组合的初始化

package acherie.resuing;

class Window {

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

    Window (int marker) {
        System.out.println("Window(" + marker + ")");
    }
}

class House {

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

    Window w1 = new Window(1);// Before constructor
    House () {
        System.out.println("House()");
        w3 = new Window(33);
    }

    Window w2 = new Window(2);
    void f() {
        System.out.println("f()");
    }

    Window w3 = new Window(3);
}

public class OrderOfInitialization {
    public static void main(String[] args) {
        House h = new House();
        h.f();
    }
}

运行结果如下:

House static
Window static
Window(1)
Window(2)
Window(3)
House()
Window(33)
f()

Process finished with exit code 0

其实,使用反编译软件(如:jd-gui,或 intellij idea 自带的)看一看编译后生成的代码,很多问题就清楚了:

// House.class
package acherie.resuing;

import acherie.resuing.Window;

class House {
    Window w1 = new Window(1);
    Window w2 = new Window(2);
    Window w3 = new Window(3);

    House() {
        System.out.println("House()");
        this.w3 = new Window(33);
    }

    void f() {
        System.out.println("f()");
    }

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

// OrderOfInitialization.class
package acherie.resuing;

import acherie.resuing.House;

public class OrderOfInitialization {
    public OrderOfInitialization() {
    }

    public static void main(String[] args) {
        House h = new House();
        h.f();
    }
}

四、示例:继承的初始化

package acherie.resuing;

class Person {
    private int i = 8;

    protected int j;

    static {
        System.out.println("Person 静态初始化子句");
    }

    {
        System.out.println("Person 实例初始化子句");
    }

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

    Person(int i) {
        System.out.println("Person(int), i=" + i);
    }

    int k = printInit("Person.k 初始化");

    static int m = printInit("static Person.m 初始化");

    {
        System.out.println("Person 后置实例初始化语句");
    }

    static int printInit(String s) {
        System.out.println(s);
        return 47;
    }

}

public class Student extends Person {

    Student () {
        super(1);
        System.out.println("Student()");
    }

    Student(int i) {
        this();
        System.out.println("Student(int), i=" + i);
    }

    public static void main(String[] args) {
        new Person(3);
        System.out.println("--------------------------");
        Student student = new Student(2);
    }

    public static int marker = printInit("Student.marker 初始化");

    {
        System.out.println("Student 实例初始化域");
    }
}

运行结果如下:

Person 静态初始化子句
static Person.m 初始化
Student.marker 初始化
Person 实例初始化子句
Person.k 初始化
Person 后置实例初始化语句
Person(int), i=3
--------------------------
Person 实例初始化子句
Person.k 初始化
Person 后置实例初始化语句
Person(int), i=1
Student 实例初始化域
Student()
Student(int), i=2

Process finished with exit code 0

使用反编译软件查看编译后的 class 文件:

// Person.class
package acherie.resuing;

class Person {
    private int i = 8;
    protected int j;
    int k;
    static int m;

    Person() {
        System.out.println("Person 实例初始化子句");
        this.k = printInit("Person.k 初始化");
        System.out.println("Person 后置实例初始化语句");
        System.out.println("Person()");
    }

    Person(int i) {
        System.out.println("Person 实例初始化子句");
        this.k = printInit("Person.k 初始化");
        System.out.println("Person 后置实例初始化语句");
        System.out.println("Person(int), i=" + i);
    }

    static int printInit(String s) {
        System.out.println(s);
        return 47;
    }

    static {
        System.out.println("Person 静态初始化子句");
        m = printInit("static Person.m 初始化");
    }
}

// Student.class
package acherie.resuing;

import acherie.resuing.Person;

public class Student extends Person {
    public static int marker = printInit("Student.marker 初始化");

    Student() {
        super(1);
        System.out.println("Student 实例初始化域");
        System.out.println("Student()");
    }

    Student(int i) {
        this();
        System.out.println("Student(int), i=" + i);
    }

    public static void main(String[] args) {
        new Person(3);
        System.out.println("--------------------------");
        new Student(2);
    }
}

六、总结

本文主要介绍了类和示例如何初始化的,并辅以示例。其实,在研究这个问题时最大的感受在于,通过查看编译后生成的 class 文件,其实很多不懂的东西都能够解决和更好的理解。

七、参考链接

  1. 《深入理解 Java 虚拟机》
  2. Java 101: Class and object initialization in Java
  • 31
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值