【问题标题】:Why doesn't the Java garbage collector collect garbage?为什么 Java 垃圾收集器不收集垃圾?
【发布时间】:2012-07-28 20:56:34
【问题描述】:

我在玩一个可怕的数据结构,它基本上是一棵树,每个节点都将对其子节点的引用存储在 HashMap 对象中。每当我需要摆脱根及其除一个之外的所有子树时,我都会遇到释放内存的麻烦,方法是将后一个子树设置为新根。我认为这可能是我的数据结构中的一些错误,也许是我忘记在那里的一些参考,所以没有任何东西符合垃圾收集的条件。但我想先尝试一些更简单的东西,并实现了以下测试:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

public class MyNode {
        MyNode next;
        int somedata;

        public MyNode(MyNode n) {
            next = n;
            somedata = 0;
        }

        public static void main(String[] args) throws IOException {
            MyNode p = new MyNode(null);
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            for (int i=0; i<10000000; i++) {
                MyNode n = new MyNode(p);
                p = n;
            }
            while (p!=null) {
                MyNode p1 = p.next;
                p.next = null;
                p = p1;
            }
            in.readLine();
        }
}

当 main 到达 in.readline() 时,我可以在 htop 中看到该进程仍然为自己分配了 250MB 左右的空间,并且没有任何东西被释放。我显然首先尝试简单地做

p = null;

而不是 while 循环。但它不起作用,所以我想出了以前的代码。

【问题讨论】:

  • 小心 - 分配给进程的内存量与用于对象的内存量相同。大多数进程在分配内存后不会将内存返回给操作系统,因此即使垃圾收集器确实运行了,您也不一定会看到分配给进程的总内存反映了它。
  • 在 Java 命令行应用选项 -XX:+UseConcMarkSweepGC 以使用并发收集器。否则我认为 HotSpot 只会在分配时触发集合。
  • 使用 HTOP 绝对是衡量 java 任务内存使用的错误方法,您需要使用分析器 - 如果您使用的是 JDK 1.6 或更高版本,那么 VisualVM 附带 JDK
  • 我使用 HTOP 是因为我对我的程序使用的实际内存感兴趣。但我想我明白了,可能 Java 不是完成这种分配/释放工作的最佳工具
  • Lorenzo,如果您事先知道您真正需要多少内存,那么只需将 Java 的最大堆限制为该大小。这将迫使 GC 更加雄心勃勃地回收内存。如果堆上有可回收的内存,您可以绝对确定 GC 不会允许内存不足的情况。

标签: java garbage-collection


【解决方案1】:

您的问题有些不清楚 - 您是否真的遇到了 OutOfMemoryErrors?你想解决什么问题?您看到测试用例中表现出的行为有几个原因:

  1. 当某些内容不再可从 GC 根访问时,不会收集垃圾 - 它只是变得有资格进行垃圾收集。 GC 通常仅在分配失败时触发。由于您实际上并没有在循环中分配更多内存以使引用无效,因此完全有可能 GC 尚未运行。

  2. 即使垃圾被收集,堆中的内存通常也不会返回给操作系统 - 因此从操作系统的角度来看它不会产生准确的答案。使用 VisualVM 或 jmap 和 jhat 之类的工具将是找出实际仍在堆中的内容的最佳方法。

【讨论】:

    【解决方案2】:

    垃圾收集器的行为很复杂,不同的垃圾收集器可以使用根本不同的方法。你不能指望垃圾会被立即回收,即使你明确表示 调用它。

    【讨论】:

      【解决方案3】:

      我以前见过这个。

      htop 不是衡量 JVM 内存利用率的最佳方法。一直显示高分。 VM 还喜欢保持其堆分配尽可能高。

      我是否建议使用visualgc 或jconsole 来跟踪JVM 附带的jps 和jpstat。 http://java.sun.com/performance/jvmstat/#Tools

      另外,你有一个快速创建大量对象的循环,所以 gc 现在可能有时间启动。 System.gc 不能保证实际执行。这是一个提示,但通常有效。

      还可以查看这篇关于 Java 堆利用率的帖子: http://it.toolbox.com/blogs/lim/how-to-get-details-on-jvms-heap-utilization-10609

      【讨论】:

        【解决方案4】:

        您可以使用代码在 10000 次循环中手动调用一次垃圾回收

        System.gc();
        

        但是有一些副作用,例如垃圾收集器使用额外的 cpu 时间。

        【讨论】:

        • 这个其实不推荐。你可以让整个虚拟机暂停几秒钟。
        • 实际上 JVM 决定何时为这个调用提供服务。垃圾收集器在被调用的那一刻就被调用不是强制性的。所以从某种意义上说是安全的。但我同意这是一种糟糕的编程习惯。
        • @user1559260 这不是强制性的,但除非您指定 -XX:+ExplicitGCInvokesConcurrent,否则 HotSpot 确实会在调用 gc() 时停止所有线程。
        • @user1559260 你错了。 Javadoc 明确指出“当控制从方法调用返回时,虚拟机已尽最大努力回收所有丢弃的对象。”这是一个同步调用。
        • 您的意思是,当我在 while 循环中将所有引用设置为 null 时?我尝试在循环结束时调用 System.gc() 一次。没有。如果我每 10k 循环调用一次,它为什么会做任何事情?