OutOfMemoryError:Java 堆空间。如何修复递归方法中发生的这个错误?

Posted

技术标签:

【中文标题】OutOfMemoryError:Java 堆空间。如何修复递归方法中发生的这个错误?【英文标题】:OutOfMemoryError: Java Heap Space. How can I fix this error that happens in a recursive method? 【发布时间】:2011-10-16 06:18:00 【问题描述】:

我有一个 Java 应用程序,它解析目录及其子目录中的 pdf 文件,并使用文件中的信息创建数据库。

当我在大约 900 个文件上使用该程序时,一切都很好(它创建了一个包含多个表的 SQLite 数据库,其中一些包含 150k 行)。

现在我试图在更大的数据集(大约 2000 个文件)上运行我的程序,并且在某些时候我得到“OutOfMemoryError: Java Heap space”。我在 jdev.conf 文件中更改了以下行:

AddVMOption  -XX:MaxPermSize=256M

到 512M,我得到了同样的错误(虽然后来,我想)。我将再次将其更改为更大的东西,但问题是将使用该程序的计算机要旧得多,因此没有那么多内存。通常,用户一次不会添加超过 30 个文件,但我想知道我应该将它们限制在多少个文件。理想情况下,无论要解析多少文件,我都希望我的程序不抛出错误。

起初,我认为是我的 SQLite 查询导致了错误,但在 Google 上阅读后,它可能是一些递归函数。我将它(我认为它至少是正确的)隔离到这个函数中:

 public static void visitAllDirsAndFiles(File dir) 
      if(dir.isDirectory()) 
      
        String[] children = dir.list();
        for (int i=0; i<children.length; i++) 
        
          visitAllDirsAndFiles(new File(dir, children[i]));
        
      
      else
      
        try
                  
          BowlingFilesReader.readFile(dir);
        
        catch(Exception exc)
        
          exc.printStackTrace();
          System.out.println("Other Exception in file: " + dir);
        
      
  

我认为问题可能在于它为每个后续目录递归调用此函数,但我真的不确定这可能是问题所在。你怎么看?如果可能的话,我怎样才能做到,这样我就不会再收到这个错误了?如果您认为不可能仅此部分导致问题,我将尝试找出程序的其他部分可能导致问题。

我能看到的唯一另一件事是我在调用上述方法之前连接到数据库并在它返回后断开连接。这样做的原因是,如果我在每个文件之后连接和断开连接,我的程序解析数据需要更长的时间,所以我真的不想改变它。

【问题讨论】:

PermGen 空间不是堆空间。是复制粘贴错了选项,还是增加了错误的选项? 哦,顺便说一句,您可以将迭代语法简化为for (String s : children) visitAllDirsAndFiles(new File(dir, s)); 你确定你的程序没有内存泄漏? 可能你忘记关闭所有 I/O,在 finally 块中 如果遇到这种情况,另一种方法是编写不使用递归的例程。 【参考方案1】:

MaxPermSize 只会改变您的永久空间。您的堆空间不足。使用 -Xmx 属性增加最大堆大小

【讨论】:

【参考方案2】:

如果问题的根源是递归,您将收到与堆栈而不是堆相关的错误。似乎您在BowlingFilesReader 中存在某种内存泄漏...

【讨论】:

是的,就像我在 OP 中评论的那样,在每个人都告诉我这不是导致错误的递归之后,我试图找到泄漏并发现我的读者没有正确关闭时抛出了一个异常,所以我把它们放在 finally 块中。谢谢!【参考方案3】:

我建议你尝试使用类似的东西来增加堆空间

-mx1000m

如果您有 64 位 JVM,您最多可以使用机器总内存的 80%。如果您有 32 位 JVM,则可能会被限制在 1200 到 1400 MB 左右,具体取决于操作系统。

【讨论】:

【参考方案4】:

BowlingFilesReader.readFile(dir); 很可疑。它加载到内存中的量是多少,为什么?如果它将一个相当大的目录中的所有文件加载到内存中,那就是一个问题。

你也可以试试

java -Xmx 1G 或更多,取决于您的 RAM 情况。

您总是可以尝试使用堆栈而不是递归函数。

S = []
while( !S.isEmpty() )
   S.pop()
   //operate
   S.push( all of the current item's children )

【讨论】:

变量目录实际上是一个文件,当它到达那一行时,所以这不是问题。至于堆栈,这是一个好主意,即使我刚刚发现递归不是问题。它可能比进行长时间运行的递归调用更有效。 是的。使用堆栈或队列代替递归函数调用是算法问题竞赛中非常常见的策略。对于问题所需的数据类型,递归函数通常会变得太深太快。请注意,队列将为您提供 BFS 而不是 DFS。【参考方案5】:

我认为您应该下载一份内存分析工具MAT。完成堆转储后,将其加载到 MAT 中,运行 Leak Suspect 报告,您应该能够很快找出问题所在。

【讨论】:

【参考方案6】:

@Adam Smith 回答您的问题

The same problem happened... I'm going to close my ResultSets, 
PreparedStatements and Statements now, but can you explain 
why I have to close them? Don't they get de-allocated when 
the method returns (thus they're no longer in the scope of any methods)? 

大多数 Jave IDE 都有内置的 JProfiler 或可用插件,集成您的项目,使用分析器运行,然后您将看到运行时中存在的所有对象,没有什么复杂的

那么你必须关闭:

File I/O 示例 here , JDBC Introduction (页面底部的示例),并检查并避免打开大量连接(不仅是 JDBC Conn),创建一个并重用它,如果一切都完成了,你可以关闭这个 Conn 也一样,(连接在双方,PC 和服务器上都是困难且缓慢的操作),所有 Streamed Object 必须在 The finally Block 中关闭,因为总是有效

正如我所提到的,这些对象从未从 JVM UsedMemory 中消失,而且大多数...从未被 GC(有关更多详细信息,请在此论坛上搜索),GC 永远不会立即工作

    Runtime runtime = Runtime.getRuntime();
    long total = runtime.totalMemory();
    long free = runtime.freeMemory();
    long max = runtime.maxMemory();
    long used = total - free;   
    System.out.println(Math.round(max / 1e6) + " MB available before Cycle");
    System.out.println(Math.round(total / 1e6) + " MB allocated before Cycle");
    System.out.println(Math.round(free / 1e6) + " MB free before Cycle");
    System.out.println(Math.round(used / 1e6) + " MB used before Cycle");
    //.... your code with 
    //.....
    runtime = Runtime.getRuntime();
    long total = runtime.totalMemory();
    long free = runtime.freeMemory();
    long max = runtime.maxMemory();
    long used = total - free;
    System.out.println(Math.round(max / 1e6) + " MB available past Cycle");
    System.out.println(Math.round(total / 1e6) + " MB allocated past Cycle");
    System.out.println(Math.round(free / 1e6) + " MB free past Cycle");
    System.out.println(Math.round(used / 1e6) + " MB used past Cycle");        

    runtime = Runtime.getRuntime();
    runtime.gc();

    //dealyed with some Timer ... 
    long total = runtime.totalMemory();
    long free = runtime.freeMemory();
    long max = runtime.maxMemory();
    long used = total - free;
    System.out.println(Math.round(max / 1e6) + " MB available after GC");
    System.out.println(Math.round(total / 1e6) + " MB allocated after GC");
    System.out.println(Math.round(free / 1e6) + " MB free after GC");
    System.out.println(Math.round(used / 1e6) + " MB used after GC"); 

有关此论坛的更多信息和 :-) 用英语描述 :-)

【讨论】:

谢谢,这非常有用,我会记住这一点,我相信它会帮助我不再犯同样的错误!非常感谢您的帮助!

以上是关于OutOfMemoryError:Java 堆空间。如何修复递归方法中发生的这个错误?的主要内容,如果未能解决你的问题,请参考以下文章

java.lang.OutOfMemoryError:Java 堆空间 [重复]

java.lang.OutOfMemoryError : Java 堆空间

异常 java.lang.OutOfMemoryError:Java 堆空间

java.lang.OutOfMemoryError:DBeaver 中的 Java 堆空间 [重复]

java.lang.OutOfMemoryError:Maven 中的 Java 堆空间

java.lang.OutOfMemoryError超出Java堆空间的GC开销限制?