【问题标题】:Why am I getting 'java.lang.OutOfMemoryError: GC overhead limit exceeded' if I have tons of free memory given to the JVM?如果我有大量的空闲内存分配给 JVM,为什么会出现“java.lang.OutOfMemoryError:GC 开销限制超出”?
【发布时间】:2012-03-29 14:49:47
【问题描述】:

我正在学习和测试一些图形库并且遇到了一个奇怪的问题(但这不是特定于图形的,我很确定这是一般的 Java 相关的)。我得到:'java.lang.OutOfMemoryError:超过 GC 开销限制'。 I understand this error means that garbage collecting spending most of the cpu time and not returning any memory 但我不知道如何解决这个问题。

基本上我(出于学习目的)想看看在内存中创建大量图形节点需要多长时间。我的系统正在运行美分,我有 7 gigs 的 ram,但程序从未超过 25%(我可以通过“顶部”看到),即使我通过运行“java -jar jungtester”将系统中的所有内存都给了它.jar -Xmx7g -XX:+UseConcMarkSweepGC -XX:-UseGCOverheadLimit'(jungtester.jar 是我的程序)。似乎它没有使用所有可用内存并在大约 350 万个节点后死亡,这很奇怪,因为它只是一个 for 循环,所以我认为它只是在添加节点之前一直在不停地添加节点,直到内存已满。

我对 JVM 的内部工作很陌生,所以任何关于如何克服这个问题的建议都会很棒。

如果有帮助,代码如下:

import edu.uci.ics.jung.graph.DirectedGraph;
import edu.uci.ics.jung.graph.DirectedSparseGraph;

public class main {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println("starting...");
        long startTime = System.currentTimeMillis();
        DirectedGraph<Integer,Integer> graph = new DirectedSparseGraph<Integer, Integer>();;
        graph.addVertex(1);
        graph.addVertex(2);
        graph.addEdge(1, 1,2);

        for (int i = 0; i < 1000000; i++) {
            graph.addVertex(i);
            //System.out.println(i + " means we are " + (float) i/1000000 + "% done.");
        }
        long endTime1 = System.currentTimeMillis();
        System.out.println("done adding 1000000 in " + (endTime1 - startTime));


        for (int i = 1000001; i < 10000000; i++) {
            graph.addVertex(i);
            graph.addEdge(i, i, i-1000000);
            System.out.println(i + " means we are " + (float) i/1000000000 + "% done.");
        }

        long endTime = System.currentTimeMillis();

        System.out.println("It took " + (endTime - startTime));
    }

}

更新:我让它工作了,我不知道为什么,但顺序很重要。我上面的命令在我放 -jar 之后没有采取任何措施,但是当我将 -jar 添加到末尾时,它似乎可以工作。

【问题讨论】:

  • 使用 jconsole(如果你有 X 可用)在你的应用程序运行时监控 JVM,它会告诉你发生了什么。 Top 在向您显示 JVM 实时统计数据方面几乎没有 jconsole 那样精细。
  • 您是在 32 位还是 64 位机器上运行它? 32 位系统无法使用 7G 内存。
  • @PéterTörök - 没错,但如果您在 32 位机器上使用 -Xmx7g,JVM 将不会启动。所以我认为这不是问题。
  • 抱歉,我在命令行上运行它(我没有可用的图形)@PéterTörök 它是 64 位机器。
  • @learningJava 是 64 位 JVM 吗?

标签: java


【解决方案1】:

我认为这里发生的事情是您的程序性质和您选择的 GC 设置的不幸结果

基本上,您的程序正在非常快速地创建大量节点,并且(看起来)在运行结束之前永远不会释放它们中的任何一个。因此,每次 GC 运行时,它必须跟踪和撤离它正在处理的“来自”空间中的每个对象。这项工作与空间中的物体数量成正比。随着您的应用程序的进展,空间越来越大,对象的数量越来越大,并且越来越多的时间用于 GC 移动对象。您正在运行的 CMS 收集器比吞吐量收集器的开销更大,这可能会加剧这种情况。

这可能解释了为什么您似乎只使用了 25% 的可用内存。但是,您从top 获得的数字也可能具有误导性。我更倾向于相信你从 jconsole 等人那里得到的数字。

我还会打开 GC 日志记录,看看是否发生了一些奇怪的事情。例如,您的应用程序的行为可能会压倒 CMS 收集器,并导致 JVM 切换到 stop-the-world GC。 (我隐约记得听说当这种情况发生时你的性能会受到很大影响,这可能足以导致 JVM 达到 GC 开销限制。)


那么,您可以做些什么来改进运行此应用程序的 JVM 的行为。我建议如下:

  • 尝试使用吞吐量收集器而不是 CMS。
  • 如果您的 JVM 有能力,请尝试使用新的 G1 收集器。
  • 也将初始堆大小设置为 7GB:使用 -Xms7g

仔细分析 GC 日志可能会提出其他尝试的建议。

但也许你能做的最好的事情就是放弃这个(我希望的)不切实际的基准。

【讨论】:

  • 你也许是对的。我注意到的是,在一分钟内,内存使用率上升到 25%,然后一分钟后它给了我错误。我将研究如何进行日志记录,我会在没有 CMS 收集器的情况下尝试它(我使用它是因为我在链接的问题中读到它可能允许我使用更多内存)
【解决方案2】:

我绝不是专家,但这是我的猜测。

如果它在内存完全耗尽之前不执行 GC,它会...

A) ... 消耗非常大/不必要的大量内存

B) ... 在实际必须执行 GC 的时间受到巨大的性能影响。

所以,它确实在内存用完之前执行 GC,而这个 GC 过程无法跟上您在可能很深的对象图中创建和丢弃的数百万个节点.

【讨论】:

  • 但是 -XX:-UseGCOverheadLimit 不是告诉它根本不做 GC 吗?在我链接的问题中,人们写道,这将导致问题不被检查。另外,你又引发了几个问题,它是如何判断某个东西太大或不必要的?据它所知,我可以在其他地方使用这些数据?有没有办法关闭它?如果问题没有意义,我对 JVM 正在做的后台任务感到很困惑。
  • -UseGCOverheadLimit 很好。我没有一个好的答案。关于您的后续问题:这些事情也可以使用-XX-options 进行微调。例如,您有 -XX:MaxHeapFreeRatio=70 (source)。
猜你喜欢
  • 1970-01-01
  • 2012-04-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-24
  • 1970-01-01
  • 2015-02-12
相关资源
最近更新 更多