3

我正在阅读java中的弱引用,听起来很简单,如果一个对象只有弱引用,那么它可以被垃圾收集器收集。除非您的参考在您使用该值之前已失效,否则会发生什么?

例子:

假设我有一个带有键 {1,2,3,4,5} 的弱哈希图,所有值都为 1。现在假设您有一个用于 [1:10] 中数字的随机数生成器。现在每次获取数字时,它都会检查它是否是映射中的键,然后对该键提供临时强引用。因此,使用此设置,您将拥有一些具有强引用的键并因此保留在内存中,但您也有可能某些键在被选择之前就已失效。

如果我对弱哈希映射的直觉是正确的,这是否意味着映射将在某个时候从其原始状态改变?

4

3 回答 3

4

尝试使用Integer对象作为 a 的键WeakHashMap可能会导致一些奇怪的行为。首先,javadoc forWeakHashMap有以下注释:

此类主要用于关键对象,其 equals 方法使用 == 运算符测试对象身份。一旦这样的键被丢弃,它就永远无法重新创建,因此以后不可能在 WeakHashMap 中查找该键并惊讶于它的条目已被删除。此类将非常适用于其 equals 方法不基于对象标识的关键对象,例如 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 is based on ==,那么您的问题并不真正适用-如果您不再对键有强引用,则该值是否从地图中删除并不重要,因为您不再有办法要做到这一点-您无法重新创建使用基于身份的相等性的密钥。

于 2018-08-16T22:54:38.707 回答
1

这个答案已经解决了在构造中使用具有基于值的相等类型的问题,该构造的行为取决于对象的身份,例如可达性。

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

但是,您也可以将弱可达对象转回强可达状态,例如通过调用get()a 的方法WeakReference或在迭代 a 的映射时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));

具有不同标识且没有覆盖equals方法 via的对象的构造new Object()可确保不存在对同一对象的其他引用或相等对象。在这段代码的某一时刻,该对象只是弱可达,但随后,它以非常高的可能性变得强可达。

在这些点之间可能会发生垃圾收集,并且由于对对象的所有弱引用都被自动清除,因此您可以通过nullget(). 不过,此时发生垃圾收集的可能性非常低。这就是为什么链接的答案使用中间调用来System.gc()提高弱引用被清除的可能性。

这是一个相当人为的例子,但有助于解决您的问题,“......这是否意味着地图将在某个时候从其原始状态改变?”。

如果您使用具有不同身份的相同键或在一段时间内难以访问的键,则映射可能会在某个时候更改,但不能保证这种情况会发生。这取决于垃圾收集器何时运行并实际发现某些对象的弱可达性。但通常,JVM 会尝试阻止垃圾收集,直到真正需要它为止。因此,应用程序可能会在没有垃圾收集的情况下安静地运行一段时间。此外,如果您确实定期轮询映射,甚至可能发生 gc 在查找期间键可强烈访问的那个时间点运行。

于 2018-08-21T12:06:52.983 回答
0

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

话虽如此,仅将某些内容放入弱哈希映射而不继续使用密钥作为强引用是没有意义的,因为收集器可以在您访问它之前立即收集它。

它可能不会(甚至System.gc()不会强制 GC 运行),但您不能依赖它。

于 2018-08-17T06:24:38.057 回答