当实际对象被垃圾回收时,WeakHashMap 条目中的值如何被垃圾回收?

How does a value in an entry in the WeakHashMap gets garbage collected when the actual object is garbage collected?

首先,我想澄清一下我对 WeakReference 的理解,因为下面的问题取决于相同的问题。

static void test() {
    Person p = new Person();
    WeakReference<Person> person = new WeakReference<>(p);
    p = null;
    System.gc();
    System.out.println(person.get());
    System.out.println(person);
}

static class Person {
    String name;
}

static class PersonMetadata {
    String someData;

    public PersonMetadata(String met) {
        someData = met;
    }
}

以上代码的输出为

null java.lang.ref.WeakReference@7852e922

这意味着虽然有一个实际的人对象在 GC 运行后被垃圾收集,但内存中有一个 WeakReference<Person> class 的对象,它没有指向任何东西这点。

现在考虑到上述理解是正确的,我对 WeakHashMap<K,V> 是如何工作的感到困惑。在下面的代码中

public static void main(String[] args) {
    Person p = new Person();
    p.name = "John";
    WeakHashMap<Person, PersonMetadata> map = new WeakHashMap<>();
    PersonMetadata meta = new PersonMetadata("Geek");
    map.put(p, meta);
    p = null;
    System.gc();
    if (map.values().contains(meta)) {
        System.out.println("Value present");
    } else {
        System.out.println("Value gone");
    }
}

static class Person {
    String name;
}

static class PersonMetadata {
    String someData;

    public PersonMetadata(String met) {
        someData = met;
    }
}

输出: Value gone

现在问题据说WeakHashMap<K,V>中的键是一个弱引用,这意味着在上面的代码中 p 变为 null 实际对象可以被垃圾回收,因为不再有对该对象的强引用,但是 作为对象的值如何处理PersonMetadata class 正在被垃圾收集 因为第一个代码证明 WeakReference class 的对象没有被垃圾收集,即使收集了实际的对象。

你误解了情况。当 map.values().contains(meta) 或短 map.containsValue(meta) returns false 时,并不意味着 meta 已被垃圾回收。事实上,您持有对 meta 中对象的引用,甚至将该引用传递给可能调用 equalscontains 方法。那么该对象如何被垃圾收集?

响应仅告诉您地图的其中一个键与该对象没有任何关联,并且由于唯一的键已被垃圾回收,所以这是正确答案。或者,您可以只要求 map.isEmpty() 检查关联是否存在。

这是 WeakHashMap 提供的内容:

Hash table based implementation of the Map interface, with weak keys. An entry in a WeakHashMap will automatically be removed when its key is no longer in ordinary use. More precisely, the presence of a mapping for a given key will not prevent the key from being discarded by the garbage collector, that is, made finalizable, finalized, and then reclaimed. When a key has been discarded its entry is effectively removed from the map, so this class behaves somewhat differently from other Map implementations.

条目的删除不是即时的。它依赖于将 WeakReference 排入 ReferenceQueue,然后在您进行下一个查询时在内部进行轮询,例如 containsValue 甚至 size()。例如。如果我将您的程序更改为:

Person p = new Person();
WeakHashMap<Person, PersonMetadata> map = new WeakHashMap<>();
PersonMetadata meta = new PersonMetadata("Geek");
map.put(p, meta);
WeakReference<?> ref = new WeakReference<>(p);
p = null;
while(ref.get() != null) System.gc();
System.out.println(map.containsValue(meta)? "Value present": "Value gone");

它偶尔会打印“Value present”,尽管关键 Person 实例在此时已被垃圾收集。如上所述,这是关于地图的内部清理,而不是关于我们在 meta 中持有强引用的 PersonMetadata 实例。

使 PersonMetadata 符合垃圾回收条件是完全不同的事情。如前所述,每当我们调用方法时,WeakReference 都会进行内部清理。如果我们不这样做,就不会进行清理,因此即使密钥已被垃圾回收,它仍然是一个强引用。考虑:

Person p = new Person();
WeakHashMap<Person, PersonMetadata> map = new WeakHashMap<>();
PersonMetadata meta = new PersonMetadata("Geek");
map.put(p, meta);
WeakReference<?> personRef = new WeakReference<>(p);
WeakReference<?> metaRef = new WeakReference<>(meta);
p = null;
meta = null;
while(personRef.get() != null) System.gc();
System.out.println("Person collected");
for(int i = 0; metaRef.get() != null && i < 10; i++) {
    System.out.println("PersonMetadata not collected");
    System.gc();
    Thread.sleep(1000);
}
System.out.println("calling a query method on map");
System.out.println("map.size() == "+map.size());
System.gc();
System.out.println("PersonMetadata "+(metaRef.get()==null? "collected": "not collected"));

哪个会打印

Person collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
calling a query method on map
map.size() == 0
PersonMetadata collected

演示 WeakHashMap 如何保持对已收集键的值的强引用,直到我们最终对其调用方法,使其有机会执行其内部清理。

WeakHashMap 和我们的方法都没有对它进行引用时,该值最终被收集。当我们删除 meta = null; 语句时,地图最后仍然是空的(在其内部清理之后),但不会收集该值。

请务必牢记,这些代码示例仅用于演示目的并触及特定于实现的行为,最值得注意的是,main 方法通常在未优化的情况下运行。形式上,如果引用未被使用,则不需要局部变量来防止垃圾收集,这一点在优化方法后在实践中具有相关性。