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

来源:互联网 发布:淘宝售后怎么投诉 编辑:程序博客网 时间:2024/04/30 23:21

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");

       public static void main(String[] args) {

              CacheTest cachetest = new CacheTest();

              cachetest.test();

              CacheKey windowKey = new CacheKey(1, "windows");

              CacheKey systemKey = new CacheKey(2, "system");

              System.out.println("because the windowsKey and systemKey were"

                            + " discarded by the garbage collector, "

                            + "obtaining cache return null.");

              System.out.println(Cache.obtain(windowKey));

              System.out.println(Cache.obtain(systemKey));

              System.out.println("because the WINDOW_KEY and SYSTEM_KEY were"

                            + " not discarded by the garbage collector, "

                            + "obtaining cache return cached object.");

              System.out.println(Cache.obtain(WINDOW_KEY));

              System.out.println(Cache.obtain(SYSTEM_KEY));

              System.out.println("because the windows and system string were"

                            + " recreatable,obtaining cache return cached object.");

              System.out.println(Cache.obtainRecreatable("windows"));

              System.out.println(Cache.obtainRecreatable("system"));

              System.out.println("END");

       }

       public void test() {

              CacheKey windowKey = new CacheKey(1, "windows");

              CacheKey systemKey = new CacheKey(2, "system");

              Cache.cache(windowKey, windows);

              Cache.cache(systemKey, system);

              Cache.cache(WINDOW_KEY, windows);

              Cache.cache(SYSTEM_KEY, system);

              Cache.cacheRecreatable("windows", windows);

              Cache.cacheRecreatable("system", system);

              System.out.println("because the keys are not being discarded"

                            + " by the garbage collector, "

                            + "obtaining cache return cached objects.");

              System.out.println(Cache.obtain(windowKey));

              System.out.println(Cache.obtain(systemKey));

              System.out.println(Cache.obtain(WINDOW_KEY));

              System.out.println(Cache.obtain(SYSTEM_KEY));

              System.out.println(Cache.obtainRecreatable("windows"));

              System.out.println(Cache.obtainRecreatable("system"));

       }

}

上面的代码的控制台输出如下:

because the keys are not being discarded by the garbage collector, obtaining cache return cached objects.

C:/windows

C:/windows/system32

C:/windows

C:/windows/system32

C:/windows

C:/windows/system32

because the windowsKey and systemKey were discarded by the garbage collector, obtaining cache return null.

null

null

because the WINDOW_KEY and SYSTEM_KEY were not discarded by the garbage collector, obtaining cache return cached object.

C:/windows

C:/windows/system32

because the windows and system string were recreatable,obtaining cache return cached object.

C:/windows

C:/windows/system32

END

更普遍的情况是Cache的对象的生命周期没有进行正式的定义,那么随着时间的推移,cache的对象的价值在降低,在这种情况下,需要在对Cache进行清理。这可以通过后台线程或者在新加入entry的时候移除最旧的对象。这可以通过LinkedHashMapremoveEldestEntry来实现。

Returns true if this map should remove its eldest entry. This method is invoked by put and putAll after inserting a new entry into the map. It provides the implementer with the opportunity to remove the eldest entry each time a new one is added. This is useful if the map represents a cache: it allows the map to reduce memory consumption by deleting stale entries.

Sample use: this override will allow the map to grow up to 100 entries and then delete the eldest entry each time a new entry is added, maintaining a steady state of 100 entries.

 

     private static final int MAX_ENTRIES = 100;

 

     protected boolean removeEldestEntry(Map.Entry eldest) {

        return size() > MAX_ENTRIES;

     }

 

This method typically does not modify the map in any way, instead allowing the map to modify itself as directed by its return value. It is permitted for this method to modify the map directly, but if it does so, it must return false (indicating that the map should not attempt any further modification). The effects of returning true after modifying the map from within this method are unspecified.

This implementation merely returns false (so that this map acts like a normal map - the eldest element is never removed).

样例代码:

package com.googlecode.javatips4u.effectivejava.eliminate;

import java.io.File;

import java.util.LinkedHashMap;

public class LinkedHashMapCache {

       private static LinkedHashMap<String, File> cache = new LinkedHashMap<String, File>() {

              private static final int MAX_SIZE = 5;

              protected boolean removeEldestEntry(

                            java.util.Map.Entry<String, File> eldest) {

                     if (size() > MAX_SIZE) {

                            return true;

                     }

                     return false;

              };

       };

       public static void cache(String key, File value) {

              cache.put(key, value);

       }

       public static File obtain(String key) {

              return cache.get(key);

       }

       public static void main(String[] args) {

              File entry = new File("c:/");

              LinkedHashMapCache.cache("a", entry);

              LinkedHashMapCache.cache("b", entry);

              LinkedHashMapCache.cache("c", entry);

              LinkedHashMapCache.cache("d", entry);

              LinkedHashMapCache.cache("e", entry);

              System.out.println(LinkedHashMapCache.cache.size());

              LinkedHashMapCache.cache("f", entry);

              System.out.println(LinkedHashMapCache.cache.size());

       }

}

控制台输出:

5

5

 

另一个内存泄漏的情况是监听器和其他的回调。当实现了一个监听器,客户端可以注册回调函数,但是没有取消注册,那么这些对象就会积累,除非api中采取一些动作。确保回调对象被GC的最好方法就是使用weak reference来存储对回调对象,例如将它们作为WeakHashMapkey

此情况与上述的WeakHashMap类似。

原创粉丝点击