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