现在的位置: 首页 > 综合 > 正文

Effective Java Item6-消除作废的对象引用

2013年05月26日 ⁄ 综合 ⁄ 共 3715字 ⁄ 字号 评论关闭

Effective Java 2nd Edition Reading Notes

Item6: Eliminate Obsolete Object References

消除作废的对象引用。

 

C或者C++中需要程序员自己进行内存管理,而Java通过使用GC来自动管理不再使用的对象。但是在有些时候,显式的消除废弃的对象应用是必要的。

例如在下面的代码中,StackInRisk类中的pop方法并没有将pop的对象引用消除删除,从而导致即使在其他的地方不再引用pop出去的对象(stack增大后再缩小),该对象仍然不会被GC回收。这样将导致性能的下降,甚至发生内存溢出异常(虽然这种情况非常罕见)

package com.googlecode.javatips4u.effectivejava.eliminate;

import java.util.EmptyStackException;

public class StackInRisk {

       private Object[] elements;

       private int size = 0;

       private static final int DEFAULT_INITIAL_CAPACITY = 16;

       public StackInRisk() {

              elements = new Object[DEFAULT_INITIAL_CAPACITY];

       }

       public void push(Object e) {

              ensureCapacity();

              elements[size++] = e;

       }

       public Object pop() {

              if (size == 0) {

                     throw new EmptyStackException();

              }

              // memory leak here, did not remove the object in the elements array.

              return elements[--size];

       }

       /**

        * Ensure space for at least one more element, roughly doubling the capacity

        * each time the array needs to grow.

        */

       private void ensureCapacity() {

              if (elements.length == size) {

                     Object[] elementsNew = new Object[size * 2 + 1];

                     System.arraycopy(elements, 0, elementsNew, 0, size);

                     elements = elementsNew;

              }

       }

}

这是由于Stack维持了对这些对象的作废的引用。作废的引用是指不会被解除的引用。一个被作废引用的对象不仅仅不会被GC回收,该对象引用的其他对象也不会被GC回收,依次类推。所以有时即使只是一部分对象引用是作废引用,那么将导致非常多的对象不能被回收。

上面对Stack解决内存泄漏问题的方法是将pop出去的对象解引用。

//     public Object pop() {

//            if (size == 0)

//                   throw new EmptyStackException();

//            Object result = elements[--size];

//            elements[size] = null; // Eliminate obsolete reference

//            return result;

//     }

显式的将该对象引用设置为null的好处在于,如果程序再次使用该引用进行操作,那么将会抛出NullPointerException,而不是什么都不做。

 

但是并不是什么时候都需要进行显式的null操作。显式的null化应该是特定的情况才进行的,而不是正常的情况下使用。(毕竟GC可以做绝大部分的操作)。最好的消除废弃引用的办法是:使包含引用的变量超出作用域,通过在最小的范围内定义变量引用。

[通过将变量定义在最小的作用域范围内,可以增强代码的可读性和可维护性]

那么到底什么时候需要进行显式的null化操作呢?

一般说来,当一个类本身进行内存的管理操作时,例如上面提到的Stack。需要注意内存泄漏。

另外一个内存泄漏的情况是缓存。当将一个对象放入缓存中,很容易忘记它的存在,直到它变得无关。

一般的解决方式是使用WeakHashMap来表示Cache

public class WeakHashMap<K,V>extends AbstractMap<K,V>implements Map<K,V>

A hashtable-based Map implementation with weak keys. An entry in a WeakHashMap will automatically be removed when its key is no longer in ordinary use. More precisely, the presence of a mapping for a given key will not prevent the key from being discarded by the garbage collector, that is, made finalizable, finalized, and then reclaimed. When a key has been discarded its entry is effectively removed from the map, so this class behaves somewhat differently than other Map implementations.

This class is intended primarily for use with key objects whose equals methods test for object identity using the == operator. Once such a key is discarded it can never be recreated, so it is impossible to do a lookup of that key in a WeakHashMap at some later time and be surprised that its entry has been removed. This class will work perfectly well with key objects whose equals methods are not based upon object identity, such as String instances. With such recreatable key objects, however, the automatic removal of WeakHashMap entries whose keys have been discarded may prove to be confusing.

WeakHashMap实现了Map接口,但是它的value会在key不使用的时候自动的移除。也就是说,对于一个指定的keymap不会阻止它被GC回收,即通过finalizable->finalize->reclaimed。当key被回收后,value也会从map中移除。

但是该类是主要用于比较两个对象是否equals时,通过对象的标志即==运算来比较的。如果不是通过==来比较equals,例如String,那么String是可以被重新创建的(equals返回true的两个对象),那么WeakHashMap是不会自动删除的。示例代码:

package com.googlecode.javatips4u.effectivejava.eliminate;

import java.io.File;

public class CacheTest {

       private static final File windows = new File("C:/windows");

       private static final File system = new File("C:/windows/system32");

       // THESE KEYS MAY BE USED SOMEWHERE ELSE

       public static final CacheKey WINDOW_KEY = new CacheKey(1, "windows");

       public static final CacheKey SYSTEM_KEY = new CacheKey(2, "system");

抱歉!评论已关闭.