Effective Java 学习笔记 (四)

第五条      :消除过期的对象引用

使用Java语言也需要考虑内存管理的事情,考虑这样的例子:

Public class Stack{

         ……

         public Object pop(){

                   If(size == 0)

                            throw new EmptyStackException();

                   return       elements[--size];

         }

         ……

}

         这样的程序会导致内存泄漏,那些从stack弹出的对象将不会被当作垃圾回收。因为栈内部维护着对这些对象的过期引用,即永远也不会再被解除的引用。

         修复这个问题,只需在对象引用过期时,清空这些引用即可。上面程序改为:

public Object pop(){

                   If(size == 0)

                            throw new EmptyStackException();

                   Object result = elements[--size];

                   elements[size] = null;

                   return result;

}

         “清空对象引用”这样的操作应该时一个例外,而不是一种规范行为。消除过期引用最好的方法是重用一个本来已经包含对象引用的变量,或者让这个变量结束其生命周期。如果是在最紧凑作用域范围那定义每一个变量(29条),则这种情况自然会发生。目前的JVM实现平台上,仅仅退出定义变量的代码块是不够的,要想引用消失,必须退出包含该变量的方法。

         一般而言,只要一个类自己管理它的内存,我们就应警惕内存泄漏问题。一旦一个元素被释放,则该元素中包含的任何对象引用应该要被清空。

         内存泄漏另一个常见来源是缓存。解决方法:

1.  在缓存之外存在对某个条目的建的引用,该条目就有意义,可以使用WeakHashMap来代表缓存;当缓存中的条目过期之后,它们会自动删除。

2.  条目在运行的过程中变得越来越没价值。这时,缓存应该时不时的清除掉无用的条目。 可以由一个后台线程(通过java.util.TimerAPI)来完成,或者在加入新条目时,清除最老的条目(java.util.LinkedHashMap类的removeEldestEntry方法可实现)。

 

 

第六条      :避免使用终结函数

使用终结函数会导致不稳定的行为、更差的性能,已经移植性问题。当然也有可用之处,不过尽量避免使用。

 

finalize()的用途

垃圾回收只与内存有关。

    finalize()的需求仅在一种特殊情况:通过某种创建对象方式以外的方式为对象分配了存储空间。 这种分配内存可能采用了类似C语言中的做。这种情况发生在使用“本地方法”的情况下。目前本地方法只支持CC++。普通对象通过本地方法委托给一个本地对象(本地对等体)。

 

         当对象变得不可到达的时候,垃圾回收器会回收与改对象相关联的存储空间,不需要程序员做专门的工作。C++的析构函数还可以用来回收其他的非内存资源,而在Java中,一般用try-finally块来完成类似的工作。

         终结函数并不能保证会被及时的执行,从一个对象变得不可到达开始,到终结函数被执行,这段时间的长度是任意的、不确定的。执行终结函数的算法在不同JVM实现中大相径庭。JLS不仅不保证终结函数会被及时地执行,而且根本就不保证它们会被执行。所以,我们不应该依赖一个终结函数来更新关键性的永久状态System.gcSystem.runFinalization虽然增加了finalize执行的机会,但并不保证一定会执行。

         如果一种未被捕获的异常会在终结过程中被抛出来,那么这种异常可以被忽略,并且该对象的终结过程也会终止。非捕获的异常会使对象处于破坏状态(a corrupt state),如果另一个线程企图使用这样一个被破坏的对象,则任何不确定的行为都有可能发生。

         如果一个类封装的资源确实需要回收,我们只需提供一个显示的终止方法,并要求该类的客户在每个实例不再有用的时候调用这个方法。该实例必须记录下自己是否已经被终止了,其他方法必须检查这个记录终止状态的私有域,如果对象已经被终止之后,这些方法被调用的话,则它们应该抛出IllegalStateException

         显示终止方法的典型例子是InputStreamOutputStreamclose方法。其他还有java.util.Timer上的cancel方法等。还有Image.flush,它会释放所有与Image实例相关联的资源,但该实例仍处于可用的状态,如需要的话,会重新分配资源。

         显示终止方法常与try-finally结构结合起来使用,以确保及时终止。finally子句内部调用显示的终止方法可以保证:即使在对象被使用的时候有异常抛出,该方法也会被执行。

         Foo foo = new Foo();

         try{

                   ……

}finally{

         foo.terminate();

}

 

终结函数的两个用途:

1.              当一个对象的所有者忘记调用前面段落中建议的显示终止方法的情况下,终结函数可以充当“安全网”。

2.              在本地对等体并不拥有关键资源的前提下,终结函数是最佳工具。但当把那地对等体拥有必须被及时终止的资源,则该类应该有一个显示的终止方法。终止方法也可以是个本地方法,或者它也可以调用本地方法。

 

终结函数链不会被自动执行,如果一个类(非Object)有一个终结函数,且有一个子类改写了终结函数,那么子类的终结函数必须要手工调用超类的终结函数。

protected void finalize() throws Throwable{

         try{

                   …… //finalize subclass state

         } finally{

                   super.finalize();

         }

}

为每个将被终结的对象创建一个附加对象,然后吧终结函数放在一个匿名类中,该匿名类的唯一用途是终结其外围实例,该匿名类的每个单例就是终结函数守卫者,外围类的每一个实例都会创建这样一个守卫者。外围类的每个实例都会创建一个守卫者。外围实例在它的私有实例域中保存着一个对其守卫者的惟一引用,所以守卫者与外围实例可以同时启动终结过程。当守卫者被终结时,它执行外围实例所期望的终结行为,就好像它的终结函数时外围对象上的一个方法一样:

//守卫者用法

public class Foo{

         private final Object finalizerGuardian = new Object(){

                   protectd void finalize() throws Throwable {

                            //Finalize outer Foo object

                           

                   }

         };

         … //Remainder omitted

}

对每一个带有终结函数的非final公有类,都应该考虑使用这项技术。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值