Java对象的生命周期
在Java中,对象的生命周期包括以下几个阶段:
- 创建阶段(Created)
- 应用阶段(In Use)
- 不可见阶段(Invisible)
- 不可达阶段(Unreachable)
- 收集阶段(Collected)
- 终结阶段(Finalized)
- 对象空间重分配阶段(De-allocated)
1 创建阶段(Created)
在创建阶段,系统通过下面的几个步骤来完成对象的创建过程:
- 为对象在堆中分配存储空间
- 开始构造对象
- 从超类到子类依次对static块、static成员进行初始化,初始化的顺序按照其书写的前后顺序(比如,一个static成员在static块之前,则将static成员先实例化)
- 超类成员变量按顺序初始化,递归调用超类的构造方法
- 子类成员变量按顺序初始化,子类构造方法调用
一旦对象被创建,并被分派给某些变量赋值,这个对象的状态就切换到了应用阶段
实验
public class Test {
static{
System.out.println("Test static");
}
public static void main(String[] args) {
new E();
}
}
class A{
static{
System.out.println("A static");
}
public A(String str) {
System.out.println("A "+str);
}
}
class B{
static{
System.out.println("B static");
}
public B() {
System.out.println("B constructor");
}
}
class C{
static{
System.out.println("C static");
}
public C() {
System.out.println("C constructor");
}
}
class D {
static{
System.out.println("D static");
}
static B b = new B();
A a = new A("B's A");
public D() {
System.out.println("D constructor");
}
}
class E extends D {
static{
System.out.println("E static");
}
static C c = new C();
A a = new A("C's A");
public E() {
System.out.println("E constructor");
}
}
运行结果
Test static
D static
B static
B constructor
E static
C static
C constructor
A static
D's A constructor
D constructor
E's A constructor
E constructor
分析
- 加载Test类:由于
public static void main(String[] args)
在Test类中定义,因此,在进入main函数的执行之前,先加载Test类。在类Test的类加载的初始化阶段,执行static块,对应输出Test static
。 - 实例化一个类E对象
- 加载类D:由于类E从类D继承,因此先加载类D。在类D的类加载的初始化阶段,执行static块,并且初始化static成员变量。
- 执行类D的static块,对应输出
D static
。 - 初始化类D的static成员变量,即执行
static B b = new B();
- 加载类B,在类B的类加载的初始化阶段,执行static块,对应输出
B static
。 - 实例化类B对象。在类B加载完成后,通过调用类B的构造函数以实例化一个类B对象,对应输出
B constructor
。
- 加载类B,在类B的类加载的初始化阶段,执行static块,对应输出
- 执行类D的static块,对应输出
- 加载类E:
- 执行类E的static块,对应输出
E static
。 - 初始化类E的static成员变量,即执行
static C c = new C();
- 加载类C,在类C的类加载的初始化阶段,执行static块,对应输出
C static
。 - 实例化类C对象。在类C加载完成后,通过调用类C的构造函数以实例化一个类C对象,对应输出
C constructor
。
- 加载类C,在类C的类加载的初始化阶段,执行static块,对应输出
- 执行类E的static块,对应输出
- 实例化一个类D对象:由于类E从类D继承,因此,在实例化一个类E对象之前,需要先实例化一个类D对象
- 实例化类D对象的成员变量,对应执行
A a = new A("D's A");
- 加载类A:在类A的类加载的初始化阶段,执行static块,对应输出
A static
。 - 实例化类A对象。在类A加载完成后,通过调用类A的构造函数以实例化一个类A对象,对应输出
D's A constructor
。
- 加载类A:在类A的类加载的初始化阶段,执行static块,对应输出
- 真正实例化类D对象。在类D的成员变量加载完成后,通过调用类D的构造函数以实例化一个类D对象,对应输出
D constructor
。
- 实例化类D对象的成员变量,对应执行
- 实例化一个类E对象:
- 实例化类E对象的成员变量,对应执行
A a = new A("E's A");
- 实例化类A对象。由于类A之前已经加载过了,因此直接调用类A的构造函数以实例化一个类A对象,对应输出
E's A constructor
。
- 实例化类A对象。由于类A之前已经加载过了,因此直接调用类A的构造函数以实例化一个类A对象,对应输出
- 真正实例化类E对象。在类E的成员变量加载完成后,通过调用类E的构造函数以实例化一个类E对象,对应输出
E constructor
。
- 实例化类E对象的成员变量,对应执行
- 加载类D:由于类E从类D继承,因此先加载类D。在类D的类加载的初始化阶段,执行static块,并且初始化static成员变量。
2 应用阶段(Using)
对象至少被一个强引用(strong reference)持有着。
3 不可见阶段(Invisible)
简单说就是程序的执行已经超出了该对象的作用域了。
举例
如下面代码的System.out.println(count);
,count已经超出了其作用域,则称之为count处于不可视阶段。
当然这种情况编译器在编译的过程中会直接报错了。
boolean has = false;
while (has) {
int count = 0;
count ++;
}
System.out.println(count);
4 不可达阶段(Unreachable)
对象处于不可达阶段是指该对象不再被任何强引用所持有。
与“不可见阶段”相比,“不可见阶段”是指程序不再持有该对象的任何强引用。
5 已收集阶段(Collected)
当垃圾回收器发现该对象已经处于“不可达阶段”,并且垃圾回收器已经对该对象的内存空间重新分配做好准备时,则对象进入了“收集阶段”。
如果该对象对应的类重写了finalize()方法,则会去执行该方法。 这里要特别说明一下:不要重载finazlie()方法!原因有两点:
会影响JVM的对象分配与回收速度
在分配该对象时,JVM需要在垃圾回收器上注册该对象,以便在回收时能够执行该重载方法;在该方法的执行时需要消耗CPU时间且在执行完该方法后才会重新执行回收操作,即至少需要垃圾回收器对该对象执行两次GC。
可能造成该对象的再次“复活”
在finalize()
方法中,如果有其它的强引用再次持有该对象,则会导致对象的状态由“收集阶段”又重新变为“应用阶段”。这会破坏Java对象的生命周期。
6 终结阶段(Finalized)
当对象执行完finalize()方法后仍然处于不可达状态时,则该对象进入终结阶段。在该阶段是等待垃圾回收器对该对象空间进行回收。
7 对象空间重新分配阶段(Free)
垃圾回收器对该对象的所占用的内存空间进行回收或者再分配了,则该对象彻底消失了,称之为“对象空间重新分配阶段”。
Reference
- Java对象的生命周期 - https://www.jianshu.com/p/72a0e26d35bc