4.使用弱引用构建非敏感数据的缓存
4.1全局 Map 造成的内存泄漏
无意识对象保留最常见的原因是使用Map将元数据与临时对象(transient object)相关联。假定一个对象具有中等生命周期,比分配它的那个方法调用的生命周期长,但是比应用程序的生命周期短,如客户机的套接字连接。需要将一些元数据与这个套接字关联,如生成连接的用户的标识。在创建Socket时是不知道这些信息的,并且不能将数据添加到Socket对象上,因为不能控制 Socket 类或者它的子类。这时,典型的方法就是在一个全局 Map 中存储这些信息,如下面的 SocketManager 类所示:使用一个全局 Map 将元数据关联到一个对象。
1. public class SocketManager {
2. private Map<SOCKET,&NBSP;USER> m = new HashMap<SOCKET,&NBSP;USER>();
3.
4. public void setUser(Socket s, User u) {
5. m.put(s, u);
6. }
7.
8. public User getUser(Socket s) {
9. return m.get(s);
10. }
11.
12. public void removeUser(Socket s) {
13. m.remove(s);
14. }
15. }
这种方法的问题是元数据的生命周期需要与套接字的生命周期挂钩,但是除非准确地知道什么时候程序不再需要这个套接字,并记住从 Map 中删除相应的映射,否则,Socket 和 User 对象将会永远留在 Map 中,远远超过响应了请求和关闭套接字的时间。这会阻止 Socket 和 User 对象被垃圾收集,即使应用程序不会再使用它们。这些对象留下来不受控制,很容易造成程序在长时间运行后内存爆满。除了最简单的情况,在几乎所有情况下找出什么时候 Socket 不再被程序使用是一件很烦人和容易出错的任务,需要人工对内存进行管理。
4.2如何使用WeakHashMap
在Java集合中有一种特殊的Map类型—WeakHashMap,在这种Map中存放了键对象的弱引用,当一个键对象被垃圾回收器回收时,那么相应的值对象的引用会从Map中删除。WeakHashMap能够节约存储空间,可用来缓存那些非必须存在的数据。关于Map接口的一般用法。
下面示例中MapCache类的main()方法创建了一个WeakHashMap对象,它存放了一组Key对象的弱引用,此外main()方法还创建了一个数组对象,它存放了部分Key对象的强引用。
1. import java.util.WeakHashMap;
2.
3. class Element {
4. private String ident;
5.
6. public Element(String id) {
7. ident = id;
8. }
9.
10. public String toString() {
11. return ident;
12. }
13.
14. public int hashCode() {
15. return ident.hashCode();
16. }
17.
18. public boolean equals(Object obj) {
19. return obj instanceof Element && ident.equals(((Element) obj).ident);
20. }
21.
22. protected void finalize(){
23. System.out.println("Finalizing "+getClass().getSimpleName()+" "+ident);
24. }
25. }
26.
27. class Key extends Element{
28. public Key(String id){
29. super(id);
30. }
31. }
32.
33. class Value extends Element{
34. public Value (String id){
35. super(id);
36. }
37. }
38.
39. public class CanonicalMapping {
40. public static void main(String[] args){
41. int size=1000;
42. Key[] keys=new Key[size];
43. WeakHashMap map=new WeakHashMap();
44. for(int i=0;i< SPAN>
45. Key k=new Key(Integer.toString(i));
46. Value v=new Value(Integer.toString(i));
47. if(i%3==0)
48. keys[i]=k;
49. map.put(k, v);
50. }
51. System.gc();
52. }
53. }
从打印结果可以看出,当执行System.gc()方法后,垃圾回收器只会回收那些仅仅持有弱引用的Key对象。id可以被3整除的Key对象持有强引用,因此不会被回收。