Java 类初始化(详解)

Java 类初始化(详解)

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

一、类加载时机

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

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

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

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

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

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

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

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

2.1 类初始化

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

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

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

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

2.2 实例初始化

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

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

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

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

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

父类类初始化 ();子类类初始化 ();父类 () + 父类构造器;子类 () + 子类构造器;

三、示例:组合的初始化

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 文件,其实很多不懂的东西都能够解决和更好的理解。

七、参考链接

《深入理解 Java 虚拟机》Java 101: Class and object initialization in Java

相关文章

苹果iPhone 16e处理器和iPhone 16是一样的吗
365bet在线登录

苹果iPhone 16e处理器和iPhone 16是一样的吗

📅 07-02 👁️ 7152
饥荒机械零件怎么得
365bet在线登录

饥荒机械零件怎么得

📅 07-29 👁️ 7979
我的手机闹铃在哪里找 手机闹钟设置
365bet网站地址

我的手机闹铃在哪里找 手机闹钟设置

📅 07-26 👁️ 6732