5

考虑创建 5-6 个线程的应用程序,每个线程在循环中为 5mb 页面大小分配 MappedByteBuffer。

MappedByteBuffer b = ch.map(FileChannel.MapMode.READ_ONLY, r, 1024*1024*5);

迟早,当应用程序处理大文件时,会抛出 oom

java.io.IOException: Map failed  at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:758)
Caused by: java.lang.OutOfMemoryError: Map failed
        at sun.nio.ch.FileChannelImpl.map0(Native Method)
        at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:755)

根据规范,MappedBuffer 应该在它本身被 GC 时立即处理直接内存。看起来问题是,MappedBuffer-s GC-ed 太晚了,然后直接内存就完成了。

如何避免这种情况?可能会说 MappedBuffer 隐式处理或使用某种 MappedBuffer 池

4

4 回答 4

6

您可以通过直接清理映射的字节缓冲区来避免触发 GC。

public static void clean(ByteBuffer bb) {
    if(bb == null) return;
    Cleaner cleaner = ((DirectBuffer) bb).cleaner();
    if(cleaner != null) cleaner.clean();
}

如果您在丢弃之前调用它,您将不会耗尽虚拟内存。

也许您可以减少创建更大的 ByteBuffers(除非您有大量文件) 创建 MappedByteBuffer 不是免费的(在某些机器上大约需要 50 微秒)

于 2011-12-18T22:10:19.750 回答
3

错误消息显示“映射失败”,而不是“堆空间”或“永久空间”。这意味着 JVM 没有足够的可用地址空间

请参阅Sun 数据库中的这个错误,以及这个问题

第一个链接提供了一个解决方法(ewww),它与第二个链接所说的内容相近:

    try {
        buffer = channel.map(READ_ONLY, ofs, n);
    } catch (java.io.IOException e) {
        System.gc();
        System.runFinalization();
        buffer = channel.map(READ_ONLY, ofs, n);
    }
于 2011-12-18T17:03:42.053 回答
2

也许WeakHashMap汇集那些MappedBuffers会起作用。

但在您猜测根本原因之前,我建议您将您的应用程序连接到Visual VM 1.3.3并安装所有插件,这样您就可以确切地看到导致 OOM 错误的原因。您假设这些 MappedBuffers 正在这样做,但对于 5-6 个线程,它们每个只有 5MB - 总共 25-30MB。

有数据比猜测好。Visual VM 将为您提供。

于 2011-12-18T16:47:08.990 回答
0

MappedBuffer 应该在它本身是 GC 时立即处理直接内存

它实际上并没有在我能看到的任何地方这么说。有一个长期存在的 Bug Parade 项目说它永远不会发布。

它确实这样说:

因此,建议将直接缓冲区主要分配给大的、长寿命的缓冲区

于 2011-12-19T05:51:01.570 回答