弱引用的可维护性

3
我在阅读有关Java中弱引用的内容时发现,如果一个对象只有弱引用,那么它可以被垃圾收集器回收。但是,如果您在使用该值之前引用变为无效,会发生什么呢?
例如:
假设我有一个弱哈希映射,其键为{1,2,3,4,5},所有键的值均为1。现在假设您有一个生成[1:10]数字的随机数生成器。每次获取数字时,它会检查它是否是映射中的键,并给予该键临时强引用。因此,通过这种设置,您将拥有一些具有强引用并因此留在内存中的键,但您也有一些键在被选择之前可能会变为无效。
如果我的弱哈希映射直觉是正确的,那么这是否意味着映射将在某个时间点被更改以脱离其原始状态?
3个回答

4
尝试将Integer对象用作WeakHashMap的键可能会导致一些奇怪的行为。首先, WeakHashMap的javadoc 中有以下注意事项:

该类主要用于使用其等于方法使用==operator测试对象标识的键对象。一旦这样的键被丢弃,它就无法重新创建,因此不可能在稍后的某个时间在WeakHashMap中查找该键并惊讶地发现其条目已被删除。与基于对象标识而不是基于对象值的等于方法的键对象一起,此类将完全正常工作,例如String实例。但是,对于这样可重现的键对象,自动删除其键已被丢弃的WeakHashMap条目可能会导致混淆。

请考虑以下代码:
    WeakHashMap<Integer, String> map = new WeakHashMap<>();
    Integer k = Integer.valueOf(9001);
    map.put(k, "OVER 9000!?");

    while (true)
    {
        System.out.println(map.get(k));
        Thread.sleep(100);
        k = Integer.valueOf(9001);
        System.gc();
    }

循环将从打印“OVER 9000!?”开始,但在第一次循环后,原始键已被丢弃(即使现在有一个与之 equals 的键的引用)。因此,如果该键对象被垃圾回收,该条目将从映射中删除,循环将开始打印“null”。由于我们在丢弃键后调用了 System.gc(); ,因此这可能发生在单个循环之后。
然而,使用 Integer 作为 WeakHashMap 键的问题并没有结束。如果将上面的值9001更改为1,则会发现行为发生了变化! (可能吗?这可能取决于实现。)现在,该条目永远不会从地图中删除。这是由于整数缓存 - Integer.valueOf(1)始终返回相同的 Integer 实例,但 Integer.valueOf(9001)每次都创建一个新的 Integer 实例。
第二个问题仅适用于 Integer ,但第一个问题实际上适用于任何尝试使用基于 == 而非基于 equals 的键的方案。如果 equals 基于 == ,则您的问题实际上并不适用-如果您不再具有对键的强引用,则无论该值是否从地图中删除都无关紧要,因为您无法创建使用基于标识的相等性的键。

这篇文章的重点并不是你所回答的内容。我想知道的是,在某个时间段内无法获取密钥是否会导致在我有机会使用它之前将其删除。 - Fancypants753
最后一段讨论了这个问题。如果您能够以一致的方式获取密钥,那么肯定已经有某些东西对其保持了强引用,这就是一个无意义的问题(只要您的密钥使用基于身份的相等性)。 - VeeArr
那么弱引用适用于你将某人访问密钥的情况(通过向他们提供堆栈分配的强引用),当堆栈关闭时(他们完成操作),缺少强引用使得收集它成为公平竞争? - Fancypants753
强引用不仅可以来自堆栈,还可以来自堆。你所说的其余部分通常是正确的——我通常看到WeakHashMap被用于缓存关于其键使用的对象的元数据或计算数据。当这些对象无法再被访问(例如,因为使用它们的代码已经完成),那么这些元数据也就不再需要,并且可以在这些对象被垃圾回收时自动丢弃。 - VeeArr

1

这篇答案已经解决了与使用基于值相等的类型在依赖于对象身份(如可达性)的构造中出现问题的相关问题。

简而言之,当您能够构建具有与弱可达键相同的相等性的新对象时,就可以检测到键的突然移除。

然而,您也可以将弱可达对象转换回强可达状态,例如通过调用WeakReferenceget()方法或迭代WeakHashMap的映射。

WeakHashMap<Object, Boolean> map = new WeakHashMap<>();
Object key = new Object();
map.put(key, true);
WeakReference<Object> ref = new WeakReference<>(key);
key = null;
// now, the key object is only weakly reachable
key = ref.get();
// now, the key object might be strongly reachable again
// in that case, this statement will print true
System.out.println(map.get(key));

使用new Object()构建具有独特标识且未覆盖equals方法的对象,确保不存在对相同对象或相等对象的其他引用。在代码的某个点上,该对象仅为弱可达,并且随后以非常高的概率变为强可达。
在这些点之间可能会发生垃圾回收,由于所有对对象的弱引用都是原子性清除的,因此您可以通过从get()获得null引用来检测此情况。尽管如此,在此时进行垃圾回收的可能性非常低。那就是为什么链接的答案在其中使用了对System.gc()的调用,以增加清除弱引用的可能性。
这只是一个推导出的例子,但可以帮助解决您的问题,“这是否意味着地图最终会从其原始状态改变?”
如果您使用相同的键但具有不同的身份或某些时间内弱可达的键,则映射可能会在某个时候发生更改,但并不保证一定会发生。这取决于垃圾收集器何时运行并实际发现某些对象的弱可达性。但通常,JVM 会尝试防止垃圾收集,直到真正需要它为止。因此,应用程序可能在完全没有进行垃圾收集的情况下运行很长一段时间。此外,如果您经常轮询映射,则甚至可能发生,在查找期间键是强可达的那一刻 gc 正好运行。

0

WeakReference 的目的是帮助内存管理。正如你所写的,“如果对象没有被正常使用”(实际上没有直接变量持有它的强引用),“那么你就不再需要它了”(它可以被垃圾回收)。在弱哈希映射的情况下,它适用于键,因此通常用于缓存临时关联数据。

从这个意义上讲,仅将某些内容放入弱哈希映射中而不继续使用键作为强引用是没有意义的,因为收集器可能会在你访问它之前立即收集它。

它可能不会(即使 System.gc() 也不能强制运行 GC),但你不能依赖它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接