首页 > 解决方案 > System.gc() 收集仍然被局部变量引用的对象

问题描述

当我运行以下程序时

public static void main(String[] args) {

    ArrayList<Object> lists = new ArrayList<>();
    for (int i = 0; i <200000 ; i++) {
        lists.add(new Object());
    }
    System.gc();
    try {
        Thread.sleep(Integer.MAX_VALUE);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

我转储堆

jmap -dump:live,format=b,file=heap.bin 27648
jhat -J-Xmx2G heap.bin

ArrayList200000 个对象丢失。

不知道为什么JVM知道对象不会被使用,为什么JVM判断这个GC根不是引用。

标签: javagarbage-collectionjvm

解决方案


局部变量本身不是 GC 根。Java® 语言规范定义:

可达对象是可以从任何活动线程的任何潜在持续计算中访问的任何对象。

很明显,它需要一个包含对对象的引用的变量,以便可以在活动线程的“潜在持续计算”中访问它,因此可以将此类变量的缺失用作易于检查的标志一个对象是不可到达的。

但这并不排除额外的努力来识别仍然被局部变量引用的无法访问的对象。规范甚至明确指出

可以设计优化程序的转换,将可到达的对象的数量减少到比那些天真地认为是可到达的要少。例如,Java 编译器或代码生成器可能会选择将不再使用的变量或参数设置为 null,以使此类对象的存储空间可能更快地被回收。

在同一部分。

是否确实如此,取决于当前执行模式等条件,即方法是解释运行还是已经编译。

从 Java 9 开始,您可以插入显式障碍,例如

public static void main(String[] args) {
    ArrayList<Object> list = new ArrayList<>();
    for (int i = 0; i <200000 ; i++) {
        list.add(new Object());
    }
    System.gc();
    try {
        Thread.sleep(Integer.MAX_VALUE);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    Reference.reachabilityFence(list);
}

这将强制列表保持活动状态。

以前的 Java 版本的替代方法是同步:

public static void main(String[] args) {
    ArrayList<Object> list = new ArrayList<>();
    for (int i = 0; i <200000 ; i++) {
        list.add(new Object());
    }
    System.gc();
    synchronized(list) {
        try {
            Thread.sleep(Integer.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

但通常,您希望尽早收集未使用的对象。finalize()只有当您与有关可达性的幼稚假设一起使用时,才会出现问题。


推荐阅读