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

Dib*_*ndu 6 java collections garbage-collection weak-references 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

上面代码的输出是

null java.lang.ref.WeakReference@7852e922

这意味着虽然有一个实际的 person 对象在 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

输出: Value gone

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

Hol*_*ger 8

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

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

这是WeakHashMap提供的内容:

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

条目的删除不是即时的。它依赖于入队WeakReferencea 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");
Run Code Online (Sandbox Code Playgroud)

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

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"));
Run Code Online (Sandbox Code Playgroud)

哪个会打印

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");
Run Code Online (Sandbox Code Playgroud)

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

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

重要的是要记住,这些代码示例用于演示目的和触摸实现特定行为,最值得注意的是,main方法通常未优化运行。形式上,如果所指对象未使用,则不需要局部变量来防止垃圾收集,这一点在优化方法时在实践中具有相关性。

  • @Eugene 是的,发布到 `ReferenceQueue` 不是即时的,至少在 HotSpot/OpenJDK 上不是。垃圾收集器将发现的引用移交给另一个线程,该线程最终将引用入队。[魔法从这里开始](https://searchcode.com/file/123104510/src/share/classes/java/lang/ref/Reference.java#l-117)... (2认同)