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 方法也是静态方法,所以触发类初始化的时机就是:调用类的静态对象。
现在,类初始化的时机我们清楚了,那么类是如何初始化的呢?实例又是如何初始化的?涉及继承时又是如何初始化的?
二、类初始化,实例初始化
要搞懂类初始化的过程,就要搞懂类初始化和实例初始化。
类初始化
2.1 类初始化
前面已经讨论了类进行初始化的时机,下面就来说说类初始化会干些什么。
在类初始化之前的准备阶段,虚拟机会将类变量(static 修饰的变量)分配内存并设置零值。
在类初始化阶段,执行类构造器
编译器会在将 .java 文件编译成 .class 文件时,收集所有类初始化代码和 static {} 域的代码,收集在一起成为
2.2 实例初始化
构造器:保证实例正确的初始化,能被使用。
所以,既然子类继承了父类,那么子类调用构造函数初始化的,就需要调用父类的构造器,不管是显示调用父类构造器还是 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