如何重现Java OutOfMemoryError - 超出GC开销限制

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (11)

我的方法是创建数十万个本地集合,并用随机字符串填充它们,如下所示:

    SecureRandom random = new SecureRandom();
    for(int i = 0 ; i < 100000 ; i++){
        HashMap<String, String> map = new HashMap<String, String>();
        for(int j = 0 ; j < 30 ; j++){
            map.put(new BigInteger(130, random).toString(32), new BigInteger(130, random).toString(32));
        }
    }

我也提供了-XX:+ UseGCOverheadLimit jvm参数,但无法获取该错误。有没有简单可靠的方法/黑客得到这个错误?

提问于
用户回答回答于

既然你没有接受任何答案,我会假设他们都没有为你工作。这是一个会的。但首先,回顾触发此错误的条件

如果在垃圾收集中花费了太多时间,并行收集器将抛出一个OutOfMemoryError:如果超过总时间的98%用于垃圾收集,并且小于2%的堆被恢复

所以,你必须消耗几乎所有的堆,保持它的分配,然后分配大量的垃圾。把很多东西放进Map去不会为你做这件事。

public static void main(String[] argv)
throws Exception
{
    List<Object> fixedData = consumeAvailableMemory();
    while (true)
    {
        Object data = new byte[64 * 1024 - 1];
    }
}


private static List<Object> consumeAvailableMemory()
throws Exception
{
    LinkedList<Object> holder = new LinkedList<Object>();
    while (true)
    {
        try
        {
            holder.add(new byte[128 * 1024]);
        }
        catch (OutOfMemoryError ex)
        {
            holder.removeLast();
            return holder;
        }
    }
}

consumeAvailableMemory()方法用相对较小的内存块填充堆。“相对较小”是非常重要的,因为JVM会将“大”对象(我的经验为512k字节)直接放入终身代,让年轻一代空着。

在我消耗了大部分堆之后,我只是分配并放弃。这个阶段较小的块大小非常重要:我知道我将有足够的内存用于至少一次分配,但可能不会超过两次。这将保持GC活跃。

运行此操作会在一秒之内产生所需的错误:

> java -Xms1024m -Xmx1024m GCOverheadTrigger
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at GCOverheadTrigger.main(GCOverheadTrigger.java:12)

而且,为了完整起见,这里是我正在使用的JVM:

> java -version
java version "1.6.0_45"
Java(TM) SE Runtime Environment (build 1.6.0_45-b06)
Java HotSpot(TM) 64-Bit Server VM (build 20.45-b01, mixed mode)

现在我的问题是:为什么你想要这样做?

用户回答回答于

这个:

HashMap<String, String> map = new HashMap<String, String>();

循环范围,并且在循环迭代时没有外部(长期)对映射创建的引用。因此,每个循环迭代结束时,每个映射都有资格进行垃圾回收。

您需要在循环创建一个对象集合,并使用循环来填充该集合。

扫码关注云+社区