Java - 避免重复的手动垃圾收集 - mstor和javaxmail OutOfMemoryError

时间:2012-06-21 16:53:09

标签: java garbage-collection heap mstor

我正在使用mstor库来解析mbox邮件文件。有些文件的大小超过千兆字节。可以想象,这可能会导致一些堆空间问题。

有一个循环,对于每次迭代,都会检索特定的消息。 getMessage()调用是在它耗尽时尝试分配堆空间的。如果我在这个循环的顶部添加对System.gc()的调用,程序会毫无错误地解析大文件,但我意识到收集垃圾40,000次必须减慢程序的速度。

我的第一次尝试是让呼叫看起来像if (i % 500 == 0) System.gc(),以使呼叫每500条记录发生一次。我尝试提高和降低此数字,但结果不一致,通常会返回OutOfMemory错误。

我的第二个更聪明的尝试是这样的:

try {
    message = inbox.getMessage(i);
} catch (OutOfMemoryError e) {
    if (firstTry) {
        i--;
        firstTry = false;
    } else {
        firstTry = true;
        System.out.println("Message " + i + " skipped.");
    }
    System.gc();
    continue;
}

这个想法是只在抛出OutOfMemory错误时调用垃圾收集器,然后递减计数再试一次。不幸的是,在解析了几千封电子邮件后,程序才开始输出:

 Message 7030 skipped.
 Message 7031 skipped.
 ....

以及其他人的等等。

我很困惑,每次迭代如何击中收集器会返回与此不同的结果。根据我的理解,垃圾是垃圾,所有这些应该改变的是在给定时间收集多少。

任何人都可以解释这种奇怪的行为吗?有没有人建议其他方法不频繁地调用收藏家?我的堆空间最大化了。

5 个答案:

答案 0 :(得分:1)

您不应该依赖System.gc(),因为VM可以忽略它。如果你得到OutOfMemory,这意味着VM已经尝试运行GC。你可以尝试增加堆大小,改变堆中代的大小(比如你的大多数对象最后都是老代,然后你不需要太多的内存供年轻代使用),检查你的代码以确保你没有持有任何引用到你不需要的资源。

答案 1 :(得分:1)

在一般意义上调用System.gc()是浪费时间,它不保证在任何时候都做任何事情,它最多只是建议并且在大多数情况下被忽略。在OutOfMemoryException之后调用它更加无用,因为JVM在抛出异常之前已经尝试回收内存。

如果使用无法控制的第三方代码,唯一可以做的就是将命令行中的JVM堆分配增加到特定计算机可以处理的最多值。

Get started with java JVM memory (heap, stack, -xss -xms -xmx -xmn...)

答案 2 :(得分:1)

以下是我的建议:

  • 增加堆空间。这可能是最容易做到的事情。您可以使用-Xmx执行此操作。参数。
  • 查看加载邮件的API是否提供“流式传输”选项。也许您不需要立即将整个消息加载到内存中。

调用System.gc()对您没有任何好处,因为它不能保证将调用GC。实际上,这是糟糕代码的明确标志。如果您依赖System.gc()来运行代码,那么您的代码可能会被破坏。在这种情况下,您似乎是出于性能而依赖它,这表明您的代码肯定已损坏。

您永远无法确定JVM是否会尊重您的请求,您也无法告诉它如何执行垃圾收集。 JVM可能决定完全忽略您的请求(即,它不是保证)。 System.gc()是否会做到它应该做的事情,是非常不确定的。由于不保证其行为,最好不要完全使用它。

最后,您可以使用System.gc()选项禁用-XX:DisableExplicitGC的显式通话,这意味着再次无法保证您的System.gc()通话将运行,因为它可能正在已配置为忽略该显式调用的JVM上运行。

答案 3 :(得分:1)

默认情况下,mstor会缓存从ehcache缓存中的文件夹中检索到的邮件,以便更快地访问。但是,可以禁用此缓存,我建议您为大文件夹禁用它。

您可以通过在类路径的根目录中创建名为“mstor.properties”的文本文件来禁用缓存,其中包含以下内容:

mstor.cache.disabled=true

您还可以将此值设置为系统属性:

java -Dmstor.cache.disabled=true SomeProgram

答案 4 :(得分:0)

mstor库没有很好地处理消息的缓存。在做了一些研究后,我发现如果你调用Folder.close()(收件箱是我上面的文件夹对象),mstor和javaxmail会释放因getMessage()方法而缓存的所有邮件。

我使try / catch块看起来像这样:

try {
    message = inbox.getMessage(i);
    // moved all of my calls to message.getFrom(),
    // message.getAllRecipients(), etc. inside this try/catch.
} catch (OutOfMemoryError e) {
    if (firstTry) {
        i--;
        firstTry = false;
    } else {
        firstTry = true;
        System.out.println("Message " + i + " skipped.");
    }
    inbox.close(false);
    System.gc();
    inbox.open(Folder.READ_ONLY);
    continue;
}
firstTry = true;

每次命中catch语句时,手动清除缓存的邮件并重新打开文件夹需要40-50 ms。

通过每次迭代调用垃圾收集器,解析一个1.6千兆字节的文件需要57分钟。使用此逻辑,解析同一文件只需18分钟。

更新 - 降低mstor使用的内存量的另一个重要方面是缓存属性。有人已经提到将“mstor.cache.disabled”设置为true,这有帮助。今天我发现了另一个重要的属性,它大大减少了更大文件的OOM捕获量。

    Properties props = new Properties();
    props.setProperty("mstor.mbox.metadataStrategy", "none");
    props.setProperty("mstor.cache.disabled", "true");
    props.setProperty("mstor.mbox.cacheBuffers", "false");   // most important