【问题标题】:How does a value in an entry in the WeakHashMap gets garbage collected when the actual object is garbage collected?当实际对象被垃圾收集时,WeakHashMap 中的条目中的值如何被垃圾收集?
【发布时间】:2026-01-17 06:50:01
【问题描述】:

首先我想澄清我对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

这意味着虽然实际的 person 对象在 GC 运行后被垃圾回收,但内存中存在一个 WeakReference&lt;Person&gt; 类的对象,此时它不指向任何东西。

现在考虑到上述理解是正确的,我对WeakHashMap&lt;K,V&gt; 的工作原理感到困惑。在下面的代码中

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&lt;K,V&gt;中的键是弱引用,这意味着在上面的代码中,当p变成@ 987654331@ 实际对象可以被垃圾收集,因为没有更多对该对象的强引用,但是 PersonMetadata 类的对象的值如何被垃圾收集作为第一个代码证明WeakReference 类的对象即使实际对象已被回收,也不会被垃圾回收。

【问题讨论】:

  • 我已经解释了清理弱引用回答this question的方法。

标签: java collections garbage-collection weak-references weakhashmap


【解决方案1】:

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

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

这是WeakHashMap 提供的:

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

条目的删除不是即时的。它依赖于将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");

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

【讨论】:

  • 感谢@Holger 感谢您提供宝贵的意见,澄清我的疑问的部分是条目的删除不是即时的。它依赖于将 WeakReference 排入 ReferenceQueue。我接受这个作为答案。
  • 我看到它只是为了演示,但我有几个问题 1)是否有可能最后一个 System.gc 可能不会导致 meta 的集合和最后一行打印 @ 987654353@(在WeakHashMap 内部清理之后)(因为我们直到metaRef.get()==null 才循环,我感觉我们只是给了它一次机会)。 2)理论上while(personRef.get() != null) System.gc();会导致死循环吗?
  • @user7 是的,这仅用于演示,并假设在其默认配置中像 HotSpot 这样的 JVM,其中System.gc() 受到尊重并且足以清理引用。在其他环境中,最后一行可能会打印“未收集”,while(personRef.get() != null) System.gc(); 可能会永远循环。在这些环境中,示例代码不足以演示 WeakHashMap 的工作原理。事实上,不可能创建任何代码来展示其在此类环境中的行为。
  • 感谢您的澄清 :)
  • @Eugene 是的,发布到ReferenceQueue 不是即时的,至少在 HotSpot/OpenJDK 上不是。垃圾收集器会将发现的引用移交给另一个线程,该线程最终会将引用排入队列。 The magic starts here
最近更新 更多