6

首先,我想澄清我对以下问题的理解,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 运行,实际的 person 对象就会被垃圾回收,但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

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

4

1 回答 1

8

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

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

这是WeakHashMap提供的:

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

条目的删除不是即时的。它依赖于将WeakReferencea 排入队列ReferenceQueue,然后在您进行下一个查询时在内部对其进行轮询,例如containsValueor even 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每当我们调用一个方法时,它都会进行内部清理。如果我们不这样做,将不会进行清理,因此即使密钥已被垃圾收集,仍然是一个强引用。考虑:

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方法通常在未优化的情况下运行。形式上,如果所指对象未被使用,则不需要局部变量来防止垃圾收集,这一点在实践中与方法优化时相关。

于 2018-06-05T13:33:26.100 回答