OutOfMemoryError: Java堆空间.如何修复在递归方法中发生的此错误?



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

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

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

AddVMOption  -XX:MaxPermSize=256M

到512M,我得到了同样的错误(虽然后来,我认为)。我要把它改大一点,但问题是这个程序要用的电脑要老得多,因此没有那么多内存。通常情况下,用户不会一次添加超过30个文件,但是我想知道我应该限制多少个文件。理想情况下,我希望我的程序无论要解析多少个文件都不会抛出错误。

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

 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);
        }
      }
  }

我认为问题可能是它递归地为每个后续目录调用这个函数,但我真的不确定这可能是问题。你觉得呢?如果有可能,我怎么做才不会再出现这个错误呢?如果你认为不可能是这一部分单独导致了问题,我将试着找出程序的其他部分可能导致问题。

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

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

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

我建议您尝试使用

之类的东西来增加堆空间
-mx1000m

如果您有64位JVM,您可以使用机器总内存的80%。如果您使用的是32位JVM,则根据操作系统的不同,可能限制在1200到1400 MB左右。

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

你也可以试试

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

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

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

我认为您应该下载内存分析器工具MAT的副本。一旦您有了堆转储,将其加载到MAT中,运行泄漏怀疑报告,您应该能够很快发现您的问题是什么。

@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)? 

大多数java IDE都有内置的JProfiler或可用的插件,集成你的项目,使用profiler运行,然后你会看到运行时出现的所有对象,没什么复杂的

则必须关闭:

这里的文件I/O示例,JDBC介绍(示例在页面底部),并检查和避免打开大量的连接(不仅仅是JDBC Conn),创建一个并重用,如果一切都做了,你也可以关闭这个Conn,(连接是硬的和缓慢的行动在双方,在PC和服务器上),所有流对象必须在最后块关闭,因为总是工作

正如我提到的这些对象从未从JVM UsedMemory和majority中消失…从来没有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"); 

关于这个论坛的更多信息和:-)用英语描述:-)

最新更新