考虑应用程序,它创建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池
答案 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项目,表示从未发布。
确实这样说:
因此,建议主要分配直接缓冲区 对于大型,长寿命的缓冲区