【问题标题】:GC takes too much timeGC 耗时过长
【发布时间】:2018-02-16 09:27:00
【问题描述】:

我正在使用 IntelliJ 对 Java 堆进行一些测试,但出现此错误:

java.lang.OutOfMemoryError: GC overhead limit exceeded.

我知道当 GC 花费太多时间时会发生这种情况,但我不明白为什么 GC 发生如此频繁且花费如此多的时间。

设计:

在主线程中,我重复创建新对象,然后将它们放入地图中。为了防止这些对象被收集,我将这些对象全部放入地图后打印出来。

在监控线程中,每次创建新对象时都会打印当前空闲堆大小。

运行时堆大小配置:

-Xms5m -Xmx5m

测试代码:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Example {

    public static ReentrantLock lock = new ReentrantLock(true);
    public static Condition writeCondition = lock.newCondition();
    public static Condition monitorCondition = lock.newCondition();

    public static void main(String[] args) throws Exception {
        new Thread(new Monitor(lock, writeCondition, monitorCondition)).start(); // start a monitor thread
        Map<Integer, Object> map = new HashMap<>();
        for (int count = 0; count < 10000000; count++) {
            lock.lock();
            try {
                // every time create a new Object, I will use monitor thread print current free heap size
                monitorCondition.signal();
                map.put(count, new Object());
                writeCondition.await();
            } finally {
                lock.unlock();
            }
        }
        for (Map.Entry entry : map.entrySet()) {
            // keep reference to these Objects, so they will not be garbage collected.
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
        }
    }
}

class Monitor implements Runnable {

    ReentrantLock lock;
    Condition writeCondition;
    Condition monitorCondition;

    public Monitor(ReentrantLock lock, Condition writeCondition, Condition monitorCondition) {
        this.lock = lock;
        this.writeCondition = writeCondition;
        this.monitorCondition = monitorCondition;
    }

    @Override
    public void run() {
        int count = 0;
        while (true) {
            lock.lock();
            try {
                writeCondition.signal();
                long heapFreeSize = Runtime.getRuntime().freeMemory();
                System.out.println(count + "  times run monitor : ");
                System.out.println("heapFreesize : " + heapFreeSize);
                System.out.println();
                count++;
                monitorCondition.await();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}

结果:

60005  times run monitor :
heapFreesize:603896
Exception in thread "Thread-0" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.nio.CharBuffer.wrap(CharBuffer.java:373)
    at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:265)
    at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
    at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
    at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
    at java.io.PrintStream.newLine(PrintStream.java:545)
    at java.io.PrintStream.println(PrintStream.java:696)
    at hello.Monitor.run(Example.java:58)
    at java.lang.Thread.run(Thread.java:748)

【问题讨论】:

标签: java garbage-collection jvm


【解决方案1】:

当可用内存变少时,GC 会尝试通过收集堆中无法访问的对象来释放一些内存。检查一个对象是否不可访问并不是一件容易的事,而且需要一些时间。

当您的所有(或大部分)对象仍然在某处引用时,这是浪费时间,因为没有回收内存。因此,当 JVM 发现大量运行时用于运行 GC 而不是做一些有用的事情时,程序会以OutOfMemoryError 终止。

【讨论】:

  • 似乎JVM不够聪明,无法快速找到我创建的所有对象,它只是重复检查这些对象。我说的对吗?
  • 是的,这是正确的。然而,这个问题本质上是困难的;所以这不是不聪明的问题。
  • @user6690200 “JVM 不够聪明”是什么意思?您正在用新创建的对象填充地图,直到内存已满。这与聪明无关。没有要收集的对象。
  • @Holger 我的意思是,JVM 是否在查找无法访问的对象上花费了太多时间。而事实上,没有无法到达的对象。
  • @Holger 而且,抛出 OutOfMemoryError 时似乎仍有可用堆空间,我希望在没有可用堆空间之前不会抛出此错误...
【解决方案2】:

JVM 参数中,您已指定-Xms5m -Xmx5m,因此堆大小设置为5MB,即5242880 bytes。现在,由于您已经执行了循环10000000 次,因此您在Map 变量map 中分配IntegerObject 类型的10000000 对象。现在,如果您查看功能,那么您将意识到您平均为 map 中的单个条目提供了 0.524288 字节。但是你知道Integer对象的大小大于4 bytes(因为int的大小是4 bytesInteger包裹了int类型)。

简而言之,您没有为 JVM 堆分配足够的内存,这就是您收到异常的原因 java.lang.OutOfMemoryError: GC overhead limit exceeded.

注意:查看程序的以下输出后,我意识到单个条目占用了848 bytes 的内存(请参见下面的计算)。因此,您至少需要10000000 * 848 = 8480000000 bytes8088 MB 内存(物理内存或虚拟内存):

heapFreesize : 32344

4699761  times run monitor : 
heapFreesize : 31496

4699762  times run monitor : 
heapFreesize : 30648

4699763  times run monitor : 
heapFreesize : 29800

4699764  times run monitor : 
heapFreesize : 28952

4699765  times run monitor : 
heapFreesize : 28104

4699766  times run monitor : 
heapFreesize : 27256

4699767  times run monitor : 
heapFreesize : 26408

4699768  times run monitor : 
heapFreesize : 25560
...

尝试为JVM 分配更多的堆,您将不会收到此错误。有关Java HotSpot VM Options 的更多信息,请参阅herehere

计算:在提供的输出(我在上面发布)中,正在打印可用内存。现在如果你进行计算,你会发现在映射中添加一个条目后内存之间的差异是848。因此我可以说,map 中的单个条目(不是单个对象,因为有 2 个对象 - 整数类和对象类)正在消耗 848 byres,例如32344 - 31496 = 848; 31496 - 30648 = 848; 30648 - 29800 = 848 等等。

还要注意JVM 将根据需要增加-减少它的堆大小,如果你没有设置-Xms-Xmx 开关具有相同的值(因为我没有使用这些开关而执行示例运行)。

注意:您的代码似乎逻辑不正确,因为writeCondition.signal();monitorCondition.await(); 的使用不在sync 中。程序是awaiting infinitely。我认为它最终会卡住。

【讨论】:

  • 谢谢。我将循环更改为 60000,它足够小,可以分配到 5MB 空间。我仍然得到同样的错误。有什么办法可以降低对 GC 时间的限制?
  • @user6690200 对于60000 变量中的60000 条目,您至少需要48.6 MB 内存。所以我建议将JVM 参数设置为-Xms60m -Xmx60m请注意,48.6 MB 是map 保存条目所需的内存。 JVM 将需要更多内存来执行它的其他任务。
  • 非常感谢!我发现使用 60000 和 5MB 堆大小时测试结果可能会有所不同,有时可以将所有 60000 条目放入 map,有时会失败。请问,为什么说单个对象需要848字节?
  • @user6690200 在提供的输出中(由我在回答中发布),正在打印可用内存。现在如果你进行计算,你会发现在map 中添加一个条目后内存之间的差异是848。因此我可以说,map 中的 单个条目(不是单个对象,因为有 2 个对象 - Integer 类和 Object 类)正在消耗 848 byres。另请注意,JVM 将在需要时增加其堆大小,如果您没有设置 -Xms-Xmx 开关相同的值(因为我在执行示例运行时没有使用这些开关)。跨度>
猜你喜欢
  • 2013-05-16
  • 2016-12-22
  • 2013-06-16
  • 2013-02-26
  • 2011-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多