Java 理论与实践:用弱引用堵住内存泄漏-2

上一篇 / 下一篇  2012-07-27 10:01:13 / 个人分类:Java

51Testing软件测试网 dc$]&w0P9VB

  弱引用来救援了

1eN]{j)r0

G9LO,t,eF0  SocketManager 的问题是 Socket-User 映射的生命周期应当与 Socket 的生命周期相匹配,但是语言没有提供任何容易的方法实施这项规则。这使得程序不得不使用人工内存管理的老技术。幸运的是,从 JDK 1.2 开始,垃圾收集器提供了一种声明这种对象生命周期依赖性的方法,这样垃圾收集器就可以帮助我们防止这种内存泄漏 —— 利用弱引用。

+I,y ojtefv.u0

_7a~8J/C2p(t0  弱引用是对一个对象(称为 referent)的引用的持有者。使用弱引用后,可以维持对 referent 的引用,而不会阻止它被垃圾收集。当垃圾收集器跟踪堆的时候,如果对一个对象的引用只有弱引用,那么这个 referent 就会成为垃圾收集的候选对象,就像没有任何剩余的引用一样,而且所有剩余的弱引用都被清除。(只有弱引用的对象称为弱可及(weakly reachable)。)

jB [/S;zMM?+z0

1R&LYFm8G0  WeakReference 的 referent 是在构造时设置的,在没有被清除之前,可以用 get() 获取它的值。如果弱引用被清除了(不管是 referent 已经被垃圾收集了,还是有人调用了 WeakReference.clear()),get() 会返回 null。相应地,在使用其结果之前,应当总是检查 get() 是否返回一个非 null 值,因为 referent 最终总是会被垃圾收集的。

i(Jta1l I'X0

v$x9]#V4N/f0  用一个普通的(强)引用拷贝一个对象引用时,限制 referent 的生命周期至少与被拷贝的引用的生命周期一样长。如果不小心,那么它可能就与程序的生命周期一样 —— 如果将一个对象放入一个全局集合中的话。另一方面,在创建对一个对象的弱引用时,完全没有扩展 referent 的生命周期,只是在对象仍然存活的时候,保持另一种到达它的方法。51Testing软件测试网.U!}r.@_ {!{6G

51Testing软件测试网.f7ab{9A.[a;o

  弱引用对于构造弱集合最有用,如那些在应用程序的其余部分使用对象期间存储关于这些对象的元数据的集合 —— 这就是 SocketManager 类所要做的工作。因为这是弱引用最常见的用法,WeakHashMap 也被添加到 JDK 1.2 的类库中,它对键(而不是对值)使用弱引用。如果在一个普通 HashMap 中用一个对象作为键,那么这个对象在映射从 Map 中删除之前不能被回收,WeakHashMap 使您可以用一个对象作为 Map 键,同时不会阻止这个对象被垃圾收集。清单 5 给出了 WeakHashMap 的 get() 方法的一种可能实现,它展示了弱引用的使用:51Testing软件测试网 dk9h|tk:D-};M-v

4M W I7`mMEU:_0  清单 5. WeakReference.get() 的一种可能实现

$OL0g'wJC3Ao:q+{e051Testing软件测试网fK/Y-\.f/O&I

51Testing软件测试网.gY7O:f0y#n-l#[I

public class WeakHashMap<K,V> implements Map<K,V> {
Fe:Z4h[lXh7pb0    private static class Entry<K,V> extends WeakReference<K>51Testing软件测试网B _5ga'Ak*wrtv
      implements Map.Entry<K,V> {51Testing软件测试网-`0RR|#R!a:QCw$O
        private V value;
"N4@H/M;@9d$l&uL0        private final int hash;51Testing软件测试网me/U'^2Ob
        private Entry<K,V> next;51Testing软件测试网n&HZ-o5Bgr@-d
        ...51Testing软件测试网R}}~h&l9o.P0_c
    }
&r5O$xb vH0    public V get(Object key) {
ua4r J7r;KB Y0        int hash = getHash(key);
c eIDqi4T0        Entry<K,V> e = getChain(hash);51Testing软件测试网Rv#X Hxo9{D
        while (e != null) {51Testing软件测试网 z;QlC9@d/X8{'a
            K eKey= e.get();51Testing软件测试网EZu,w7_]p
            if (e.hash == hash && (key == eKey || key.equals(eKey)))51Testing软件测试网 CFfBDz!ydqp
                return e.value;51Testing软件测试网4O0i/Q{h)S
            e = e.next;
g3x'SL8n|0        }
nE^iC+E8}!d0        return null;
8obo%m u7t0    }

?V,Z s#Uq%s!D7i\/G H0  调用 WeakReference.get() 时,它返回一个对 referent 的强引用(如果它仍然存活的话),因此不需要担心映射在 while 循环体中消失,因为强引用会防止它被垃圾收集。WeakHashMap 的实现展示了弱引用的一种常见用法 —— 一些内部对象扩展 WeakReference。其原因在下面一节讨论引用队列时会得到解释。

eJ!URoo051Testing软件测试网3Y8bD6M gQ1o

  在向 WeakHashMap 中添加映射时,请记住映射可能会在以后“脱离”,因为键被垃圾收集了。在这种情况下,get() 返回 null,这使得测试 get() 的返回值是否为 null 变得比平时更重要了。51Testing软件测试网nn&^U?lw+~,@

8v HO M f0  用 WeakHashMap 堵住泄漏51Testing软件测试网PeZ.C0[)i0Q:Ce7~

51Testing软件测试网\|Y,["{ GW

  在 SocketManager 中防止泄漏很容易,只要用 WeakHashMap 代替 HashMap 就行了,如清单 6 所示。(如果 SocketManager 需要线程安全,那么可以用 Collections.synchronizedMap() 包装 WeakHashMap)。当映射的生命周期必须与键的生命周期联系在一起时,可以使用这种方法。不过,应当小心不滥用这种技术,大多数时候还是应当使用 普通的 HashMap 作为 Map 的实现。51Testing软件测试网T.ilLtU;VH g-G

&aiib'x}+YD,XLO(hh0  清单 6. 用 WeakHashMap 修复 SocketManager51Testing软件测试网M2S;t#}Dj ez3b&f W0M

^L/FCP `p,|L051Testing软件测试网 d*kC1A5]4RS8A

public class SocketManager {
jU4pNu9do,`;T.Jn+s0    private Map<Socket,User> m = new WeakHashMap<Socket,User>();
` RX7S5o/y f7JC`6I,s ~0   51Testing软件测试网H9|i Y q
    public void setUser(Socket s, User u) {51Testing软件测试网i6k'H9@?R
        m.put(s, u);
7uD(i+{C(C1U8RL-{0    }51Testing软件测试网2MmD3{q7W
    public User getUser(Socket s) {
d/S8V \7cs9e\6P&D0        return m.get(s);51Testing软件测试网J`f q0XA4o~1qc
    }51Testing软件测试网Xc+s,|c)Z
}
xT+_.X c:wT0
51Testing软件测试网3b9^-|V"F:R5td

  引用队列51Testing软件测试网Y8VAU1c#A

H0{)~Z(y5M0  WeakHashMap 用弱引用承载映射键,这使得应用程序不再使用键对象时它们可以被垃圾收集,get() 实现可以根据 WeakReference.get() 是否返回 null 来区分死的映射和活的映射。但是这只是防止 Map 的内存消耗在应用程序的生命周期中不断增加所需要做的工作的一半,还需要做一些工作以便在键对象被收集后从 Map 中删除死项。否则,Map 会充满对应于死键的项。虽然这对于应用程序是不可见的,但是它仍然会造成应用程序耗尽内存,因为即使键被收集了,Map.Entry 和值对象也不会被收集。

ho,@-r"nTp;w6g!L(@~0

tW,i^iED2Q0  可以通过周期性地扫描 Map,对每一个弱引用调用 get(),并在 get() 返回 null 时删除那个映射而消除死映射。但是如果 Map 有许多活的项,那么这种方法的效率很低。如果有一种方法可以在弱引用的 referent 被垃圾收集时发出通知就好了,这就是引用队列 的作用。

9|gu6q%a oe051Testing软件测试网hR.\V0Z(f

  引用队列是垃圾收集器向应用程序返回关于对象生命周期的信息的主要方法。弱引用有两个构造函数:一个只取 referent 作为参数,另一个还取引用队列作为参数。如果用关联的引用队列创建弱引用,在 referent 成为 GC 候选对象时,这个引用对象(不是 referent)就在引用清除后加入 到引用队列中。之后,应用程序从引用队列提取引用并了解到它的 referent 已被收集,因此可以进行相应的清理活动,如去掉已不在弱集合中的对象的项。(引用队列提供了与 BlockingQueue 同样的出列模式 —— polled、timed blocking 和 untimed blocking。)

#Aa)y\jj0

(t{SpZ0  WeakHashMap 有一个名为 expungeStaleEntries() 的私有方法,大多数 Map 操作中会调用它,它去掉引用队列中所有失效的引用,并删除关联的映射。清单 7 展示了 expungeStaleEntries() 的一种可能实现。用于存储键-值映射的 Entry 类型扩展了 WeakReference,因此当 expungeStaleEntries() 要求下一个失效的弱引用时,它得到一个 Entry。用引用队列代替定期扫描内容的方法来清理 Map 更有效,因为清理过程不会触及活的项,只有在有实际加入队列的引用时它才工作。51Testing软件测试网 eCW{aQ5T

51Testing软件测试网K;Lwx2`$N Su

  清单 7. WeakHashMap.expungeStaleEntries() 的可能实现

(~2_3\;x(BYI051Testing软件测试网tB2N,~'Snt

1Ncx+z;d'u$k!h#e0
    private void expungeStaleEntries() {51Testing软件测试网F Q.SGql
 Entry<K,V> e;
aY-nP b5s w0        while ( (e = (Entry<K,V>) queue.poll()) != null) {
'X$O!WL*pz5V4yS0            int hash = e.hash;
O\R[*zi0            Entry<K,V> prev = getChain(hash);51Testing软件测试网y_B__!K
            Entry<K,V> cur = prev;51Testing软件测试网 \ n5qahR
            while (cur != null) {
2X,r:ny3~7C9H|0                Entry<K,V> next = cur.next;
OlB OV1Pe3|I6]0                if (cur == e) {51Testing软件测试网5A$h I7v r
                    if (prev == e)51Testing软件测试网i.W+G&]:qL4|~ZD
                        setChain(hash, next);51Testing软件测试网Z]I Mkn
                    else
ID{Lx4U$M8|0                        prev.next = next;
o)E1IQ p6b0y/n0                    break;51Testing软件测试网 [,R qz2VC
                }51Testing软件测试网]3mF4qlH f
                prev = cur;
8IkAN9Gq`@L0                cur = next;51Testing软件测试网%?P5o"{"E]KS
            }
8s,v$h%{#_d9@0        }51Testing软件测试网6]8Yoe6X JC
    }

*T+~tL@+g+~0  结束语51Testing软件测试网4Kt\`^3\4Z

51Testing软件测试网vSa5o2q%mt

  弱引用和弱集合是对堆进行管理的强大工具,使得应用程序可以使用更复杂的可及性方案,而不只是由普通(强)引用所提供的“要么全部要么没有”可及性。下个月,我们将分析与弱引用有关的软引用,将分析在使用弱引用和软引用时,垃圾收集器的行为。

7CFS7Q$o6Q6J051Testing软件测试网C(p4j ]O+c\

TAG:

 

评分:0

我来说两句

Open Toolbar