public class LeakyChecksum{ private byte[] byteArray; public synchronized int getFileCheckSum(String filename){ int len = getFileSize(filename); if( byteArray == null || byteArray.length < len ) byteArray = new byte[len]; readFileContents(filename,byteArray); // 计算该文件的值然后返回该对象 } }上边的代码是类LeakyChecksum用来说明对象游离的概念,里面有一个getFileChecksum()方法用来计算文件内容校验和,getFileCheckSum方法将文件内容读取到缓冲区中计算校验和。来看内存泄漏,该类将在一个程序中被调用许多次,重用缓冲区而不是重新分配它。但是结果是,缓冲区永远不会被释放,因为它对程序来说总是可及的(除非LeakyChecksum对象被垃圾收集了)。
public class CachingChecksum{ private SoftReference<byte[]> bufferRef; public synchronized int getFileChecksum(String filename){ int len =getFileSize(filename); byte[] byteArray =bufferRef.get(); if( byteArray == null ||byteArray.length < len ){ byteArray = new byte[len]; bufferRef.set(byteArray); } readFileContents(filename,byteArray); } }CachingChecksum使用一个软引用来缓存单个对象,并让 JVM 处理从缓存中取走对象时的细节。类似地,软引用也经常用于 GUI 应用程序中,用于缓存位图图形。是否可使用软引用的关键在于,应用程序是否可从大量缓存的数据恢复。如果需要缓存不止一个对象,可以使用一个Map,但是可以选择如何使用软引用。可以将缓存作为 Map<K,SoftReference<V>> 或SoftReference<Map<K,V>>管理。后一种选项通常更好一些,因为它给垃圾收集器带来的工作更少,并且允许在特别需要内存时以较少的工作回收整个缓存。
public class SocketManager{ private Map<Socket,User> m = new HashMap<Socket,User>(); public void setUser(Socket s,User u){ m.put(s,u); } public User getUser(Socket s){ return m.get(s); } public void removeUser(Socket s){ m.remove(s); } } SocketManagersocketManager; //... socketManager.setUser(socket,user);这种方法的问题是元数据的生命周期需要与套接字的生命周期挂钩,但是除非准确地知道什么时候程序不再需要这个套接字,并记住从 Map中删除相应的映射,否则,Socket和User对象将会永远留在 Map中,远远超过响应了请求和关闭套接字的时间。这会阻止 Socket 和User 对象被垃圾收集,即使应用程序不会再使用它们。这些对象留下来不受控制,很容易造成程序在长时间运行后内存爆满。除了最简单的情况,在几乎所有情况下找出什么时候Socket不再被程序使用是一件很烦人和容易出错的任务,需要人工对内存进行管理。
public class WeakHashMap<K,V> implements Map<K,V>{ private static class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V>{ private V value; private final int hash; private Entry<K,V> next; // ... } public V get(Object key){ int hash =getHash(key); Entry<K,V> e =getChain(hash); while(e != null){ k eKey = e.get(); if( e.hash == hash&& (key == eKey || key.equals(eKey))) return e.value; e = e.next; } return null; } }调用 WeakReference.get() 时,它返回一个对 referent 的强引用(如果它仍然存活的话),因此不需要担心映射在while循环体中消失,因为强引用会防止它被垃圾收集。WeakHashMap 的实现展示了弱引用的一种常见用法——一些内部对象扩展 WeakReference。在向 WeakHashMap 中添加映射时,请记住映射可能会在以后“脱离”,因为键被垃圾收集了。在这种情况下,get() 返回 null,这使得测试 get() 的返回值是否为null变得比平时更重要了。
在 SocketManager 中防止泄漏很容易,只要用 WeakHashMap 代替 HashMap 就行了,如下边代码所示。(如果 SocketManager 需要线程安全,那么可以用 Collections.synchronizedMap() 包装 WeakHashMap)。当映射的生命周期必须与键的生命周期联系在一起时,可以使用这种方法。不过,应当小心不滥用这种技术,大多数时候还是应当使用普通的 HashMap 作为 Map 的实现。
public class SocketManager{ private Map<Socket,User> m = new WeakHashMap<Socket,User>(); public void setUser(Socket s, User s){ m.put(s,u); } public User getUser(Socket s){ return m.get(s); } }