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


问题内容

首先,我想澄清一下我的理解,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>内存中仍然有一个类对象,该对象此时不指向任何对象。

现在考虑到上述理解为真,我对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类的对象的值正在被垃圾回收,因为第一个代码证明WeakReference即使收集了实际的对象,该类的对象也未被垃圾回收。


问题答案:

您误会了情况。当map.values().contains(meta)或short
map.containsValue(meta)返回时false,并不表示meta已被垃圾回收。实际上,您在其中持有对该对象的引用meta,甚至将该引用传递给contains可能equals在其上调用的方法。那么该对象如何被垃圾回收?

该响应仅告诉您映射的键之一与该对象之间没有关联,并且由于唯一的键已被垃圾回收,因此这是正确的答案。或者,您可能只是要求map.isEmpty()检查关联的存在。

这是WeakHashMap提供的:

基于哈希表的Map接口实现,带有 弱键
。当a中的某个条目WeakHashMap不再用作其键时,该条目将自动删除。更准确地说,给定键的映射的存在不会阻止该键被垃圾收集器丢弃,即被终结化,终结化和回收。丢弃键后,其条目会从映射中有效删除,因此此类的行为与其他Map实现有所不同。

条目的删除不是瞬时的。它依赖于将排队WeakReference到a中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");

尽管Person此时已证明关键实例已被垃圾回收,但它偶尔还是打印“ Value present”
。如上所述,这与地图的内部清理有关,而不是与PersonMetadata我们始终保持强烈引用的实例有关meta

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

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方法通常未优化运行。正式地,如果未使用引用对象,则不需要局部变量来防止垃圾收集,这在优化方法时实际上具有相关性。