Java对象的生命周期

在创建一个Java类的对象之前,需要由虚拟机加载该类,然后对该类进行链接和初始化。初始化完成之后,才能创建出该Java类的新的对象实例。对象也有自己的初始化过程,主要通过调用对应Java类的特定构造方法来完成。当不再有引用指向一个对象时,这个对象成为垃圾回收器的候选。对象所占用的内存空间会在合适的时机被垃圾回收器回收。对象终止机制提供了一种方式在对象被回收之前进行清理工作。

当需要复制一个对象时,可以使用clone方法,如果需要将对象状态持久化,可以使用序列化机制来得到一个方便存储的字节流。


1、类的链接

Java虚拟机运行时会在内部维护所有可用Java类的相关信息,链接的过程就是把加载的Java类的字节码中包含的信息与虚拟机的内部信息进行合并,使得Java类的代码可以被执行。

(1)验证

用来确保Java类的字节码表示在结构上是正确的,验证过程可能会导致其他Java类或接口被加载。如果发现字节码格式不正确,会抛出java.lang.VerifyError错误。


(2)准备

这个过程会创建Java类中的静态域,并将这些域的值设为默认值。重要的环节是保证类加载时的类型安全,如果发现类型安全约束被破坏,抛出java.lang.LinkageError错误。


(3)解析

处理所加载的Java类中包含的形式引用。核实所引用的类被正确加载,所调用的方法确实存在。

引用关系处理策略:

A、提前解析:在链接时递归对依赖的所有形式引用都进行解析,缺点是性能较差

B、延迟解析:只在真正需要形式引用的时候才进行解析,解决性能差的问题(OpenJDK采用此方法)



2、类的初始化

执行Java类中的静态代码块和初始化静态域。初始化过程按静态代码块和静态域在代码出现的顺序依次进行。

在当前Java类被初始化之前,它的直接父类也会被初始化,但是该Java类所实现的接口并不会被初始化(即接口被初始化时,它的父接口不会被初始化)。

另外,当访问一个Java类或接口中的静态域时,只有真正声明这个域的类或接口才会被初始化。比如访问了父类的静态域,则只会初始化父类不会初始化子类。

可能造成类被初始化的操作:

(1)创建一个Java类的实例对象

比如Test test = new Test();


(2)调用Java类中的静态方法

比如Test.staticMethod();


(3)为类或接口中的静态域赋值

比如Test.staticField = 10;


(4)访问类或接口中声明的非常量静态域

比如Test.staticNoFinalField


(5)在一个顶层Java类中执行assert语句


(6)调用class和反射api进行反射操作



3、对象的创建与初始化

实际初始化流程是沿着继承层次结构树往上传递,完成部分初始化工作,到达Object类之后,再沿着层次结构树向下,完成其余的初始化工作。

在对象创建之前需要分配内存空间,其大小取决于该类及父类和祖先类包含的所有实例域的数量和类型。空间不足会抛出OutOfMemoryError错误。如果内存分配成功,则把新创建的对象的所有实例域都设为默认值,包括Java类本身声明的及父类声明的。但该对象还不能调用,因为类构造方法还没有被调用。

类构造器调用的三个步骤:

(1)调用父类的构造方法

分显式调用和隐式调用两种,显式调用通过super关键字来完成,如果没有显式调用则由编译器自动生成相关代码。

实际执行流是跳转到父类的构造方法,再沿着继承层次结构树依次往上跳转,直到到达Object类。整个过程是一个递归过程。


(2)初始化类中实例域的值

按照实例域的出现顺序依次初始化。


(3)执行类构造器的其他代码

完成初始化工作。


在编写构造方法时,注意不要在构造方法中调用可以被子类覆写的方法,因为如果子类覆写该方法,那么在初始化过程中调用父类构造方法时,其调用的是子类所覆盖的方法,而此时子类的构造方法中的代码还没有被执行,对象仍处于初始化过程当中,容易出错,特别是用到某些子类变量还没有初始化的时候。

class Parent {
    public Parent() {
        int average = 30 / getCount();
    }
    protected int getCount() {
        return 4;
    }
}

class Child extends Parent {
    private int count;
    public Child(int count) {
        this.count = count;
    }
    public int getCount() {
        return count;
    }
}

public class BadConstructor {
    public void test() {
        Child child = new Child(5);
    }

    //Exception in thread "main" java.lang.ArithmeticException: / by zero
    public static void main(String[] args) {
        BadConstructor bc = new BadConstructor();
        bc.test();
    }
}


4、对象终止

对象被销毁之前要正确释放资源。

A、内存资源:对象实例域所占用的内存空间,由垃圾回收器回收

B、非内存资源:程序运行时申请的其他系统资源,包括打开的文件、套接字连接、数据连接等,需要由程序显式释放。


在Java中对这两种资源的释放操作是分开处理的,对于非内存资源的释放无法以自动的方式来进行,因此引入了对象终止机制finalization来解决非内存资源的释放问题。


(1)finalize方法的基本用法

双重不确定:

A、finalize方法一定在对象的内存空间被垃圾回收器回收之前运行(Java语言规范没明确规定调用时间)

B、垃圾回收器运行的时间也不确定


这种双重不确定可能导致释放资源时产生与时间相关的错误。如果在某个时点上finalize方法碰巧被执行了,那么程序的行为是正确的;如果finalize方法没有被执行,则可能资源没有被正确释放。这种随机错误显然是不能出现在程序当中的。



(2)finalize方法与资源释放


(3)实现正确的finalize方法



5、对象复制


6、对象序列化




Java对象的生命周期,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。