【问题标题】:Out of memory error in my program我的程序中出现内存不足错误
【发布时间】:2017-10-02 11:40:44
【问题描述】:

我编写了一个程序,对对象列表(最大 800 个)进行一些数据处理。这份名单上所做的工作主要有以下几点:

  1. 大量的 SQL 查询
  2. 处理查询的数据
  3. 分组和匹配
  4. 将它们写入 CSV 文件

一切都很好,但是数据处理部分和SQL数据的大小每天都在增加,程序开始耗尽内存并经常崩溃。

为了避免这种情况,我决定将这个大列表分成几个较小的块,然后尝试在这些较小的列表上做同样的工作(我会在进入下一个小列表之前清除并取消当前的小列表)希望它会解决问题。但这根本没有帮助,程序仍然内存不足。

程序在for循环的第一次迭代中并没有耗尽内存,而是在第二次或第三次左右。

我是否正确清除并取消了 for 循环中的所有列表和对象,以便为下一次迭代释放内存?

我该如何解决这个问题?我已经把我的代码放在下面了。

任何建议/解决方案将不胜感激。

提前致谢。 干杯!

List<someObject> unchoppedList = new ArrayList<someObject>();
for (String pb : listOfNames) {
    someObject tccw = null;
    tccw = new someObject(...);
    unchoppedList.add(tccw);
}
Collections.shuffle(unchoppedList);
List<List<someObject>> master = null;
if (unchoppedList.size() > 0 && unchoppedList.size() <= 175) {
    master = chopped(unchoppedList, 1);
} else if (unchoppedList.size() > 175 && unchoppedList.size() <= 355) {
    master = chopped(unchoppedList, 2);
} else if (unchoppedList.size() > 355 && unchoppedList.size() <= 535) {
    master = chopped(unchoppedList, 3);
} else if (unchoppedList.size() > 535&& unchoppedList.size() <= 800)) {
    master = chopped(unchoppedList, 4);
}

for (int i = 0 ; i < master.size() ; i++) {
    List<someObject> m = master.get(i);
    System.gc(); // I insterted this statement to force GC
    executor1 = Executors.newFixedThreadPool(Configuration.getNumberOfProcessors());
    generalList = new ArrayList<ProductBean>();
    try {
        m.parallelStream().forEach(work -> {
            try {
                generalList.addAll(executor1.submit(work).get());
                work = null;
            } catch (Exception e) {
                logError(e);
            }
        });
    } catch (Exception e) {
        logError(e);
    }
    executor1.shutdown();
    executor1.awaitTermination(30, TimeUnit.SECONDS);
    m.clear();
    m = null;
    executor1 = null;

    //once the general list is produced the program randomly matches some "good" products to highly similar "not-so-good" products
    List<ProductBean> controlList = new ArrayList<ProductBean>();
    List<ProductBean> tempKaseList = new ArrayList<ProductBean>();
    for (ProductBean kase : generalList) {
        if (kase.getGoodStatus() == 0 && kase.getBadStatus() == 1) {
            controlList.add(kase1);
        } else if (kase.getGoodStatus() == 1 && kase.getBadStatus() == 0) {
            tempKaseList.add(kase1);
        }
    }
    generalList = new ArrayList<ProductBean>(tempKaseList);
    tempKaseList.clear();
    tempKaseList = null;

    Collections.shuffle(generalList);
    Collections.shuffle(controlList);
    final List<List<ProductBean>> compliCases = chopped(generalList, 3);
    final List<List<ProductBean>> compliControls = chopped(controlList, 3);
    generalList.clear();
    controlList.clear();
    generalList = null;
    controlList = null;

    final List<ProductBean> remainingCases = Collections.synchronizedList(new ArrayList<ProductBean>());
    IntStream.range(0, compliCases.size()).parallel().forEach(i -> {
        compliCases.get(i).forEach(c -> {
            TheRandomMatchWorker tRMW = new TheRandomMatchWorker(compliControls.get(i), c);
            List<String[]> reportData = tRMW.generateReport();
            writeToCSVFile(reportData);
            // if the program cannot find required number of products to match it is added to a new list to look for matching candidates elsewhere
            if (tRMW.getTheKase().isEverythingMathced == false) {
                remainingCases.add(tRMW.getTheKase());
            }
            compliControls.get(i).removeAll(tRMW.getTheMatchedControls());
            tRMW = null;
            stuff.clear();
        });
    });

    controlList = new ArrayList<ProductBean>();
    for (List<ProductBean> c10 : compliControls) {
        controlList.addAll(c10);
    }
    compliCases.clear();
    compliControls.clear();

    //last sweep where the program for last time tries to match some "good" products to highly similar "not-so-good" products
    try {
        for (ProductBean kase : remainingCases) {
            if (kase.getNoOfContrls() < ccv.getNoofctrl()) {
                TheRandomMatchWorker tRMW = new TheRandomMatchWorker(controlList, kase );
                List<String[]> reportData = tRMW.generateReport();
                writeToCSVFile(reportData);
                if (tRMW.getTheKase().isEverythingMathced == false) {
                    remainingCases.add(tRMW.getTheKase());
                }
                compliControls.get(i).removeAll(tRMW.getTheMatchedControls());
                tRMW = null;
                stuff.clear();
            }
        }
    } catch (Exception e) {
        logError(e);
    }

    remainingCases.clear();
    controlList.clear();
    controlList = null;
    master.get(i).clear();
    master.set(i, null);
    System.gc();
}
master.clear();
master = null;

这里是切碎的方法

static <T> List<List<T>> chopped(List<T> list, final int L) {
    List<List<T>> parts = new ArrayList<List<T>>();
    final int N = list.size();
    int y = N / L, m = 0, c = y;
    int r = c * L;
    for (int i = 1; i <= L; i++) {
        if (i == L) {
            c += (N - r);
        }
        parts.add(new ArrayList<T>(list.subList(m, c)));
        m = c;
        c += y;
    }
    return parts;
}

这是所要求的堆栈跟踪

java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:192)
    at Controller.MasterStudyController.lambda$1(MasterStudyController.java:212)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291)
    at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.execLocalTasks(ForkJoinPool.java:1040)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1058)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded
    at org.postgresql.core.Encoding.decode(Encoding.java:204)
    at org.postgresql.core.Encoding.decode(Encoding.java:215)
    at org.postgresql.jdbc.PgResultSet.getString(PgResultSet.java:1913)
    at org.postgresql.jdbc.PgResultSet.getString(PgResultSet.java:2484)
    at Controller.someObject.findControls(someObject.java:214)
    at Controller.someObject.call(someObject.java:81)
    at Controller.someObject.call(someObject.java:1)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
[19:13:35][ERROR] Jarvis: Exception:
java.util.concurrent.ExecutionException: java.lang.AssertionError: Failed generating bytecode for <eval>:-1
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:192)
    at Controller.MasterStudyController.lambda$1(MasterStudyController.java:212)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291)
    at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.execLocalTasks(ForkJoinPool.java:1040)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1058)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: java.lang.AssertionError: Failed generating bytecode for <eval>:-1
    at jdk.nashorn.internal.codegen.CompilationPhase$BytecodeGenerationPhase.transform(CompilationPhase.java:431)
    at jdk.nashorn.internal.codegen.CompilationPhase.apply(CompilationPhase.java:624)
    at jdk.nashorn.internal.codegen.Compiler.compile(Compiler.java:655)
    at jdk.nashorn.internal.runtime.Context.compile(Context.java:1317)
    at jdk.nashorn.internal.runtime.Context.compileScript(Context.java:1251)
    at jdk.nashorn.internal.runtime.Context.compileScript(Context.java:627)
    at jdk.nashorn.api.scripting.NashornScriptEngine.compileImpl(NashornScriptEngine.java:535)
    at jdk.nashorn.api.scripting.NashornScriptEngine.compileImpl(NashornScriptEngine.java:524)
    at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:402)
    at jdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:155)
    at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:264)
    at Controller.someObject.findCases(someObject.java:108)
    at Controller.someObject.call(someObject.java:72)
    at Controller.someObject.call(someObject.java:1)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded
[19:13:52][ERROR] Jarvis: Exception:
[19:51:41][ERROR] Jarvis: Exception:
org.postgresql.util.PSQLException: Ran out of memory retrieving query results.
    at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2157)
    at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:300)
    at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:428)
    at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:354)
    at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:169)
    at org.postgresql.jdbc.PgPreparedStatement.executeQuery(PgPreparedStatement.java:117)
    at Controller.someObject.lookForSomething(someObject.java:763)
    at Controller.someObject.call(someObject.java:70)
    at Controller.someObject.call(someObject.java:1)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded

【问题讨论】:

  • 你能提供堆栈跟踪吗?
  • 你给了JVM多少内存?您是否尝试过使用 VisualVM 查看内存使用情况?
  • 48GB 是我在启动此程序之前分配的大小。是的,我使用过 VisualVM,但我无法从中识别出任何东西。
  • 您给出的堆栈跟踪似乎没有出现在上面的代码中,所以我认为它们没有多大帮助。给定的代码也有点太大,但仍然不完整。您应该将其缩减为 minimal reproducible example,这表明仅运行这段代码就可以重现 OOME。最后,似乎有几个多线程问题,例如并行添加到不同步的ArrayList
  • Didier L - 我认为添加到不同步的 ArrayList 不是问题。是的,堆栈跟踪不会与代码同步,因为 OOME 发生在循环中间的某个位置。在其他一些运行中,它发生在其他地方,它不会每次都发生在同一位置。这里最大的问题是内存没有得到释放,尽管我正在清除所有列表并取消它们

标签: java arraylist java-8 out-of-memory


【解决方案1】:

好的,JVM 的 48GB 内存是相当多的(我假设您在谈论堆空间,所以-Xmx48G)。我们显然在这里谈论的是大数据集,这当然会使事情变得复杂,因为创建最少的可重现示例并不容易。

我会尝试的第一件事是更深入地了解消耗所有内存的原因。您可以使用以下选项让 Java 在内存不足时生成堆转储:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp

当您的程序因 OutOfMemoryError 而崩溃时,这应该会在 /tmp 中创建一个 java_xxxxxx.hprof 文件。

然后您可以尝试使用工具来分析此转储,尽管巨大的规模会带来挑战。例如,尝试简单地在 MAT 中打开它很可能不起作用,但有一些方法可以在命令行上运行它的一部分 - 可能在强大的服务器上远程运行。

有一些文章描述了对大堆转储的分析:

简而言之,这些说明归结为:

  • 下载并安装 MAT
  • 根据您在分析期间可用的内容配置 MAT 的内存设置(显然,更多 = 更好)
  • 它应该包含一个ParseHeapDump.sh 脚本,您可以使用它来运行一些分析并准备索引/报告文件。请注意,这当然需要很长时间。

    ./ParseHeapDump.sh /path/to/your.hprof
    ./ParseHeapDump.sh /path/to/your.hprof org.eclipse.mat.api:suspects
    ./ParseHeapDump.sh /path/to/your.hprof org.eclipse.mat.api:overview
    ./ParseHeapDump.sh /path/to/your.hprof org.eclipse.mat.api:top_components
    

然后您应该能够使用 MAT 打开生成的报告,并希望对它们有所帮助。


在您的评论中,您说 SomeObjects 列表正在使用大部分内存,并怀疑这些内存没有被释放。

根据您发布的代码,SomeObject 对象没有被释放,因为它们仍然可以通过unchoppedList 列表访问:您发布的代码中没有清除该列表,因此对m.clear() 的调用几乎没有对已用内存的影响,因为所有这些对象仍在其他地方引用。

因此,解决方案可能就像在填充主列表后添加一行 unchoppedList.clear(); 一样简单:

List<List<someObject>> master = null;
// lets also get rid of hardcoded numbers of lists
int maxListSize = 175;
int nbSublists = (unchoppedList.size() + maxListSize - 1) / maxListSize; // obtain rounded up integer division
master = chopped(unchoppedList, nbSublists);
// important: clear the unchoppedList so it doesn't keep references to *all* SomeObject
unchoppedList.clear();

针对其他 cmet 关于 ArrayList 的非线程安全使用的回应,我必须同意其他人的观点,即这通常是一个坏主意。

为了解决最明显的问题,我什至看不到在向执行者提交工作时使用 parallelStream 的充分理由。使用正常的顺序流将确保这再次是线程安全的(从而消除潜在的问题来源)。

请注意,如果此更改甚至对性能产生影响,我相信它甚至可能是积极的。

  • lambda 表达式很简单,因此执行速度非常快;并行流的理论上最大收益似乎很小
  • 每个被处理的顺序项都会启动一个新线程,直到执行程序达到最大值,因此所有核心几乎都应该立即忙碌
  • 使用并行流即使单独使用也会产生大量开销,在这种情况下,并行流线程还必须与执行线程竞争 CPU 时间

除此之外,可能还有其他并发问题;如果没有完整的程序,很难评估,但您的 writeToCSVFile(reportData); 调用看起来也可能存在问题。

【讨论】:

  • 是的,我正在处理的数据集非常庞大(~800 Gigs)。我尝试运行 VisualVm 并在程序运行时监视进程。我可以看到是什么在消耗内存。它主要是 ArrayLists 中的一些对象的 10000 个。但问题是当它进行第二次或第三次迭代时,第一次迭代的对象没有被释放(这是我认为正在发生的事情),但我不知道如何确认。因此,我开始寻找如何从那些旧的迭代对象中释放内存,看看它是否有帮助。
  • 我发现了您的问题的可疑原因,如果这确实是问题,我用一个非常简单的解决方案更新了我的答案。此外,我还添加了一个关于线程安全/并发的简短部分。
  • 是的,我也有同样的怀疑,据我所知,物体在不应该存在的地方还活着,我正在尝试你的建议,一旦我这样做了就会告诉你
猜你喜欢
  • 2020-10-17
  • 1970-01-01
  • 2015-01-27
  • 1970-01-01
  • 2012-03-08
  • 2013-02-21
  • 1970-01-01
  • 1970-01-01
  • 2022-01-12
相关资源
最近更新 更多