使用java.nio.MappedByteBuffer时防止OutOfMemory

时间:2011-12-18 16:41:35

标签: java nio out-of-memory bytebuffer filechannel

考虑应用程序,它创建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编辑太晚,后来直接内存完成。

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

4 个答案:

答案 0 :(得分: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微秒)

答案 1 :(得分:3)

错误消息显示“map failed”,而不是“heap space”或“permgen space”。这意味着JVM没有足够的地址空间

请参阅Sun数据库中的this bug以及this question

第一个链接提供了一个解决方法(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);
    }

答案 2 :(得分:2)

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

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

最好有数据而不是猜测。 Visual VM将为您提供。

答案 3 :(得分:0)

  

MappedBuffer应该在GC本身就配置直接内存

实际上并没有说我能看到的任何地方。有一个长期存在的Bug Parade项目,表示从未发布。

确实这样说:

  

因此,建议主要分配直接缓冲区   对于大型,长寿命的缓冲区