【问题标题】:When will Java WeakHashMap clean null key?Java WeakHashMap 什么时候会清理空键?
【发布时间】:2019-07-24 08:14:12
【问题描述】:

在下面的代码中 nameRef.get() 是 null ,在 name = nullSystem.gc() 之后。

import java.lang.ref.WeakReference;

public class Main {

    public static void main(String[] args) {
        String name = new String("ltt");

        WeakReference<String> nameRef = new WeakReference<>(name);    
        System.out.println(nameRef.get()); // ltt

        name = null;
        System.gc();

        System.out.println(nameRef.get());  // null
    }    
}

WeakHashMap 基于 WeakReference。最后我想map.size()会是0,其实是1。

import java.util.WeakHashMap;

public class Main2 {

    public static void main(String[] args) {
        String name = new String("ltt");
        WeakHashMap<String, Integer> map = new WeakHashMap<>();
        map.put(name, 18);
        System.out.println(map.size()); // 1

        name = null;
        System.gc();

        System.out.println(map.size());  // it's 1. why not 0 ?
    }    
}

Java WeakHashMap 何时会清理null 键?

【问题讨论】:

标签: java garbage-collection weak-references weakhashmap


【解决方案1】:

简单的答案:你不知道。

含义:当 jvm 启动并触发 GC 循环时,您无法控制。您无法控制何时实际收集符合条件的对象。

因此,您无法知道该地图的“状态”何时发生变化。

是的,另一部分是调用 System.gc() 只是对 jvm 进行垃圾收集的建议。在 standard 设置中,如果 jvm 遵循该请求或忽略它,您将拥有零控制权。例如,您需要使用非常特殊的 JVM 来改变它。

通常,gc 仅在 jvm 认为必要时才会运行。因此,只要内存不短缺,您的地图可能会将其大小保持为 1。正如 Holger 的 answer 很好地概述了即使 GC 运行也不会强制地图更新该部分。

【讨论】:

    【解决方案2】:

    正如其他人所指出的,不能保证System.gc() 会执行实际的垃圾收集。垃圾收集周期也不能保证实际收集特定的无法访问的对象。

    但在您的特定设置中,System.gc() 似乎足以收集对象,这由您的 WeakReference 示例确定。但清除弱引用与将引用加入队列不同,以允许WeakHashMap 执行其清除。正如this answer 中所解释的,WeakHashMap 的清理依赖于要排队的键引用,每次调用它的方法时都会检查它,这是您示例中的后续map.size() 调用。

    正如the documentation of WeakReference 指定的那样:

    假设垃圾收集器在某个时间点确定一个对象是弱可达的。那时,它将原子地清除对该对象的所有弱引用以及对通过强引用和软引用链可以访问该对象的任何其他弱可达对象的所有弱引用。同时它将声明所有以前的弱可达对象都是可终结的。在同一时间或稍后的某个时间,它会将那些注册到引用队列中的新清除的弱引用排入队列。

    请注意“在那个时候”如何进行清除,而“在同一时间或稍后的某个时间”如何进行排队。

    在 HotSpot JVM/OpenJDK 的情况下,垃圾收集后会异步进行排队,并且您的主线程似乎运行得太快,因此键引用尚未入队。

    插入一个小暂停可能会导致示例程序在典型环境中成功:

    import java.util.WeakHashMap;
    
    public class Main2 {
    
        public static void main(String[] args) throws InterruptedException{
            String name = new String("ltt");
            WeakHashMap<String, Integer> map = new WeakHashMap<>();
            map.put(name, 18);
            System.out.println(map.size()); // 1
    
            name = null;
            System.gc();
    
            Thread.sleep(10);
    
            System.out.println(map.size()); // 0
        }    
    }
    

    当然,这并没有改变这样一个事实,即它很适合测试,但不能保证行为,所以你不应该依赖它来编写生产代码。

    【讨论】:

      【解决方案3】:

      System.gc() 仅建议进行垃圾收集。无法保证何时以及是否会进行收集。您确实看到了,您提出了建议,但尚未完成。

      documentation

      [...] 调用gc 方法建议 Java 虚拟机花费精力回收未使用的对象,以使它们当前占用的内存可用于快速重用。 当控制从方法调用返回时,Java 虚拟机已尽最大努力从所有丢弃的对象中回收空间。 [...]

      话虽如此,除了定期检查之外,您无法知道地图中的条目何时消失。

      【讨论】:

      • “您无法知道地图中的条目何时消失”。这并不完全正确。始终可以通过 Java 代理提供自定义的“expungeStaleEntries”方法。
      • @LppEdd 好点。但可能超出了 OP 想要的范围。如果你熟悉它,你可以创建一个答案来展示它是如何工作的:)
      【解决方案4】:

      您的演示存在一些问题。实际上,System.gc()之后的大小是0。在你的情况是因为打印地图的大小GC还没有完成,所以结果是1

      【讨论】:

        猜你喜欢
        • 2019-08-06
        • 1970-01-01
        • 2012-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多