3

在下面的代码中nameRef.get()是 null , aftername = 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密钥?

4

4 回答 4

9

简单的答案:你不知道。

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

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

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

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

于 2019-03-02T13:50:20.140 回答
7

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

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

作为指定的文档WeakReference

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

注意清除是如何发生在“<em>那个时候”,而入队将发生在“<em>同时或稍后的某个时间”。

在 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
    }    
}

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

于 2019-03-05T10:50:01.087 回答
3

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

请参阅文档

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

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

于 2019-03-02T13:50:08.660 回答
0

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

于 2019-04-04T03:44:28.753 回答