如何重现 Java OutOfMemoror 错误 - 超出 GC 开销限制

2022-09-03 02:20:07

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

    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参数,但无法得到错误。是否有任何简单可靠的方法/黑客来获得此错误?


答案 1

既然你没有接受任何答案,我就假设它们都不适合你。这是一个会的。但首先,回顾一下触发此错误的条件

如果在垃圾回收中花费了太多时间,并行收集器将抛出一个 OutOfMemor 错误:如果超过 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;
        }
    }
}

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

在我消耗了大部分堆之后,我只是分配和丢弃。此阶段中较小的块大小很重要:我知道我将有足够的内存用于至少一个分配,但可能不超过两个。这将使 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)

现在我问你:你为什么要这样做?


答案 2

这:

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

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

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


推荐