0

我正在使用 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.
 ....

以此类推。

我只是对每次迭代的收集器如何返回与此不同的结果感到困惑。据我了解,垃圾就是垃圾,所有这一切都应该改变的是在给定时间收集了多少。

谁能解释这种奇怪的行为?有没有人推荐其他方式来减少呼叫收集器的频率?我的堆空间已用完。

4

5 回答 5

1

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

于 2012-06-21T17:01:16.763 回答
1

一般来说,打电话System.gc()是浪费时间,它不能保证随时做任何事情,充其量只是一个建议,在大多数情况下被忽略。在 an 之后调用它OutOfMemoryException就更没用了,因为 JVM 在抛出异常之前已经尝试过回收内存。

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

开始使用 java JVM 内存(堆、堆栈、-xss -xms -xmx -xmn...)

于 2012-06-21T17:08:31.360 回答
1

以下是我的建议:

  • 增加堆空间。这可能是最容易做到的事情。您可以使用-Xmx. 范围。
  • 查看加载消息的 API 是否提供“流式传输”选项。也许您不需要一次将整个消息加载到内存中。

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

您永远无法确定 JVM 是否会满足您的请求,并且您也无法确定它将如何执行垃圾收集。JVM 可能决定完全忽略您的请求(即,这不是保证)。是否System.gc()会做它应该做的,是相当不确定的。由于无法保证其行为,因此最好不要完全使用它。

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

于 2012-06-21T17:56:40.783 回答
1

默认情况下,mstor 将从 ehcache 缓存中的文件夹中检索到的消息进行缓存,以便更快地访问。但是,此缓存可能会被禁用,我建议对大文件夹禁用它。

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

mstor.cache.disabled=true

You can also set this value as a system property:

java -Dmstor.cache.disabled=true SomeProgram
于 2012-07-03T05:05:03.003 回答
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 毫秒才能手动清除缓存的消息并重新打开文件夹。

通过每次迭代调用垃圾收集器,解析一个 1.6 GB 的文件需要 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
于 2012-06-22T20:42:28.107 回答