在JVM运行空间中对象的整个生命周期大致可以分为个阶段创建阶段(Creation)应用阶段(Using)不可视阶段(Invisible)不可到达阶段(Unreachable)可收集阶段(Collected)终结阶段(Finalized)与释放阶段(Free)上面的这个阶段构成了 JVM中对象的完整的生命周期下面分别介绍对象在处于这个阶段时的不同情形 创建阶段在对象创建阶段系统要通过下面的步骤完成对象的创建过程 ()为对象分配存储空间 ()开始构造对象 ()递归调用其超类的构造方法 ()进行对象实例初始化与变量初始化 ()执行构造方法体 上面的个步骤中的第步就是指递归地调用该类所扩展的所有父类的构造方法一个Java类(除Object类外)至少有一个父类(Object)这个规则既是强制的也是隐式的你可能已经注意到在创建一个Java类的时候并没有显式地声明扩展(extends)一个Object父类实际上在 Java程序设计中任何一个Java类都直接或间接的是Object类的子类例如下面的代码 public class A { … } 这个声明等同于下面的声明 public class A extends javalangObject { … } 上面讲解了对象处于创建阶段时系统所做的一些处理工作其中有些过程与应用的性能密切相关因此在创建对象时我们应该遵循一些基本的规则以提高应用的性能 下面是在创建对象时的几个关键应用规则 ()避免在循环体中创建对象即使该对象占用内存空间不大 ()尽量及时使对象符合垃圾回收标准 ()不要采用过深的继承层次 ()访问本地变量优于访问类中的变量 关于规则()避免在循环体中创建对象即使该对象占用内存空间不大需要提示一下这种情况在我们的实际应用中经常遇到而且我们很容易犯类似的错误例如下面的代码 … … for (int i = ; i < ; ++i) { Object obj = new Object(); Systemoutprintln(obj= + obj); } … … 上面代码的书写方式相信对你来说不会陌生也许在以前的应用开发中你也这样做过尤其是在枚举一个Vector对象中的对象元素的操作中经常会这样书写但这却违反了上述规则()因为这样会浪费较大的内存空间正确的方法如下所示 … … Object obj = null; for (int i = ; i < ; ++i) { obj = new Object(); Systemoutprintln(obj= + obj); } … … 采用上面的第二种编写方式仅在内存中保存一份对该对象的引用而不像上面的第一种编写方式中代码会在内存中产生大量的对象应用浪费大量的内存空间而且增大了系统做垃圾回收的负荷因此在循环体中声明创建对象的编写方式应该尽量避免 另外不要对一个对象进行多次初始化这同样会带来较大的内存开销降低系统性能如 public class A { private Hashtable table = new Hashtable (); public A() { // 将Hashtable对象table初始化了两次 table = new Hashtable(); } } 正确的方式为 public class B { private Hashtable table = new Hashtable (); public B() { } } 不要小看这个差别它却使应用软件的性能相差甚远如图所示 图 初始化对象多次所带来的性能差别 看来在程序设计中也应该遵从勿以恶小而为之的古训否则我们开发出来的应用也是低效的应用有时应用软件中的一个极小的失误就会大幅度地降低整个系统的性能因此我们在日常的应用开发中应该认真对待每一行代码采用最优化的编写方式不要忽视细节不要忽视潜在的问题 应用阶段当对象的创建阶段结束之后该对象通常就会进入对象的应用阶段这个阶段是对象得以表现自身能力的阶段也就是说对象的应用阶段是对象整个生命周期中证明自身存在价值的时期在对象的应用阶段对象具备下列特征 — 系统至少维护着对象的一个强引用(Strong Reference) — 所有对该对象的引用全部是强引用(除非我们显式地使用了软引用(Soft Reference)弱引用(Weak Reference)或虚引用(Phantom Reference)) 上面提到了几种不同的引用类型可能一些读者对这几种引用的概念还不是很清楚下面分别对之加以介绍在讲解这几种不同类型的引用之前我们必须先了解一下Java中对象引用的结构层次 Java对象引用的结构层次示意如图所示 图 对象引用的结构层次示意 由图我们不难看出上面所提到的几种引用的层次关系其中强引用处于顶端而虚引用则处于底端下面分别予以介绍 .强引用 强引用(Strong Reference)是指JVM内存管理器从根引用集合(Root Set)出发遍寻堆中所有到达对象的路径当到达某对象的任意路径都不含有引用对象时对这个对象的引用就被称为强引用 .软引用 软引用(Soft Reference)的主要特点是具有较强的引用功能只有当内存不够的时候才回收这类内存因此在内存足够的时候它们通常不被回收另外这些引用对象还能保证在Java抛出OutOfMemory 异常之前被设置为null它可以用于实现一些常用资源的缓存实现Cache的功能保证最大限度的使用内存而不引起OutOfMemory再者软可到达对象的所有软引用都要保证在虚拟机抛出OutOfMemoryError 之前已经被清除否则清除软引用的时间或者清除不同对象的一组此类引用的顺序将不受任何约束然而虚拟机实现不鼓励清除最近访问或使用过的软引用下面是软引用的实现代码 … … import javalangrefSoftReference; … A a = new A(); … // 使用 a … // 使用完了a将它设置为soft 引用类型并且释放强引用 SoftReference sr = new SoftReference(a); a = null; … // 下次使用时 if (sr!=null) { a = srget(); } else{ // GC由于内存资源不足可能系统已回收了a的软引用 // 因此需要重新装载 a = new A(); sr=new SoftReference(a); } … … 软引用技术的引进使Java应用可以更好地管理内存稳定系统防止系统内存溢出避免系统崩溃(crash)因此在处理一些占用内存较大而且声明周期较长但使用并不频繁的对象时应尽量应用该技术正像上面的代码一样我们可以在对象被回收之后重新创建(这里是指那些没有保留运行过程中状态的对象)提高应用对内存的使用效率提高系统稳定性但事物总是带有两面性的有利亦有弊在某些时候对软引用的使用会降低应用的运行效率与性能例如应用软引用的对象的初始化过程较为耗时或者对象的状态在程序的运行过程中发生了变化都会给重新创建对象与初始化对象带来不同程度的麻烦有些时候我们要权衡利弊择时应用 .弱引用 弱引用(Weak Reference)对象与Soft引用对象的最大不同就在于GC在进行回收时需要通过算法检查是否回收Soft引用对象而对于Weak引用对象 GC总是进行回收因此Weak引用对象会更容易更快被GC回收虽然GC在运行时一定回收Weak引用对象但是复杂关系的Weak对象群常常需要好几次GC的运行才能完成Weak引用对象常常用于Map数据结构中引用占用内存空间较大的对象一旦该对象的强引用为null时对这个对象引用就不存在了GC能够快速地回收该对象空间与软引用类似我们也可以给出相应的应用代码 … … import javalangrefWeakReference; … A a = new A(); … // 使用 a … // 使用完了a将它设置为weak 引用类型并且释放强引用 WeakReference wr = new WeakReference (a); a = null; … // 下次使用时 if (wr!=null) { a = wrget(); } else{ a = new A(); wr = new WeakReference (a); } … … 弱引用技术主要适用于实现无法防止其键(或值)被回收的规范化映射另外弱引用分为短弱引用(Short Week Reference)和长弱引用(Long Week Reference)其区别是长弱引用在对象的Finalize方法被GC调用后依然追蹤对象基于安全考虑不推荐使用长弱引用因此建议使用下面的方式创建对象的弱引用 … … WeakReference wr = new WeakReference(obj); 或 WeakReference wr = new WeakReference(obj false); … … .虚引用 虚引用(Phantom Reference)的用途较少主要用于辅助finalize函数的使用Phantom对象指一些执行完了finalize函数并且为不可达对象但是还没有被GC回收的对象这种对象可以辅助finalize进行一些后期的回收工作我们通过覆盖Reference的clear()方法增强资源回收机制的灵活性虚引用主要适用于以某种比 java 终结机制更灵活的方式调度 premortem 清除操作 &注意 在实际程序设计中一般很少使用弱引用与虚引用使用软引用的情况较多这是因为软引用可以加速JVM对垃圾内存的回收速度可以维护系统的运行安全防止内存溢出(OutOfMemory)等问题的产生 不可视阶段在一个对象经历了应用阶段之后那么该对象便处于不可视阶段说明我们在其他区域的代码中已经不可以再引用它其强引用已经消失例如本地变量超出了其可视范围如下所示 … … public void process () { try { Object obj = new Object(); objdoSomething(); } catch (Exception e) { eprintStackTrace(); } while (isLoop) { // loops forever // 这个区域对于obj对象来说已经是不可视的了 // 因此下面的代码在编译时会引发错误 objdoSomething(); } } … … 如果一个对象已使用完而且在其可视区域不再使用此时应该主动将其设置为空(null)可以在上面的代码行objdoSomething();下添加代码行obj = null;这样一行代码强制将obj对象置为空值这样做的意义是可以帮助JVM及时地发现这个垃圾对象并且可以及时地回收该对象所占用的系统资源 不可到达阶段处于不可到达阶段的对象在虚拟机所管理的对象引用根集合中再也找不到直接或间接的强引用这些对象通常是指所有线程栈中的临时变量所有已装载的类的静态变量或者对本地代码接口(JNI)的引用这些对象都是要被垃圾回收器回收的预备对象但此时该对象并不能被垃圾回收器直接回收其实所有垃圾回收算法所面临的问题是相同的——找出由分配器分配的但是用户程序不可到达的内存块 可收集阶段终结阶段与释放阶段对象生命周期的最后一个阶段是可收集阶段终结阶段与释放阶段当对象处于这个阶段的时候可能处于下面三种情况 ()垃圾回收器发现该对象已经不可到达 ()finalize方法已经被执行 ()对象空间已被重用 当对象处于上面的三种情况时该对象就处于可收集阶段终结阶段与释放阶段了虚拟机就可以直接将该对象回收了 |