如何在Java中使用FileChannel取消映射内存中的文件?

56
我正在使用FileChannel.map()将文件("sample.txt")映射到内存中,然后使用fc.close()关闭通道。在此之后,当我使用FileOutputStream向文件写入时,出现以下错误:

java.io.FileNotFoundException: sample.txt(无法对已打开用户映射区的文件执行请求的操作)

File f = new File("sample.txt");
RandomAccessFile raf = new RandomAccessFile(f,"rw");
FileChannel fc = raf.getChannel();
MappedByteBuffer mbf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
fc.close();
raf.close();

FileOutputStream fos = new FileOutputStream(f);
fos.write(str.getBytes());
fos.close();

我猜这可能是因为关闭了FileChannel后,文件仍然映射到内存中。我是对的吗?如果是这样,我该如何将文件从内存中“取消映射”?(我在API中找不到任何方法)。 谢谢。
编辑: 看起来(添加取消映射方法)曾经作为RFE提交给Sun: https://bugs.java.com/bugdatabase/view_bug?bug_id=4724038

2
我也曾经遇到过同样的问题。在我的情况下,我试图将数千个文件映射到内存中,结果导致了 OutOfMemoryError 错误,我认为这是文件句柄耗尽的结果。由于没有工具可以取消映射,这会导致不可预测的行为,即使在 OutOfMemoryError 时,映射会尝试执行 sleep(100)System.gc() - 它只会推迟痉挛的发生。 - dma_k
13个回答

0

-2
如果可以保证映射文件缓冲区对象可以被垃圾回收,您就不需要GC整个VM来释放缓冲区的映射内存。您可以调用System.runFinalization()。这将调用映射文件缓冲区对象上的finalize()方法(如果在您的应用程序线程中没有对它的引用),从而释放映射内存。

1
不要使用finalization,而是使用try-with-resources。 - kittylyst
根据文档,finalization 是解除映射的唯一方式。除了依赖于 sun.* 类的未记录方法之外,这是问题的唯一实际解决方案。Try-with-resources 只是关闭通道,而 OP 已经执行了此操作。 - Jules
垃圾回收器不需要运行来确定哪些对象适合进行终结吗?或许最好先调用垃圾回收器,然后再运行 runFinalization 方法? - Aleksandr Dubinsky

-13

这里的正确解决方案是使用try-with-resources。

这允许将通道和其他资源的创建限定于一个块内。一旦该块退出,通道和其他资源即被清除且随后不能再使用(因为没有任何东西引用它们)。

内存映射仍然不会取消,直到下一次GC运行,但至少不会有任何悬空引用。


1
这并没有解决任何问题。问题不在于悬空引用,而在于映射本身仍然存在。 - stepancheg
1
问题在于内存映射会导致文件在操作系统级别被锁定,这意味着无法使用非内存映射操作对其进行写入或删除。这种所谓的解决方案对解决此问题毫无作用。 - Jules

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接