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

我使用FileChannel.map()将文件(“sample.txt”)映射到内存,然后使用fc.close()closures通道。 之后,当我使用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(); 

我认为这可能是由于文件仍然映射到内存,即使我closuresFileChannel 。 我对吗?。 如果是这样,我怎样才能从内存“取消映射”文件?(我无法find任何API的方法)。 谢谢。

编辑:看起来像(添加一个unmap方法)被提交作为RFE太阳一些回: http : //bugs.sun.com/view_bug.do?bug_id=4724038

MappedByteBuffer javadoc:

映射的字节缓冲区及其所表示的文件映射在缓冲区本身被垃圾收集之前保持有效。

尝试调用System.gc() ? 即使这只是一个虚拟机的build议。

可以使用以下静态方法:

 public static void unmap(MappedByteBuffer buffer) { sun.misc.Cleaner cleaner = ((DirectBuffer) buffer).cleaner(); cleaner.clean(); } 

但这是不安全的解决scheme,因为以下几点:
1)如果有人在取消映射后使用MappedByteBuffer,则导致失败
2)它依赖于MappedByteBuffer的实现细节

[WinXP,SunJDK1.6]我有一个从filechannel取得映射的ByteBuffer。 阅读完SO帖后终于设法通过思考来打电话给清洁工。 不再是文件locking挥之不去。

 FileInputStream fis = new FileInputStream(file); FileChannel fc = fis.getChannel(); ByteBuffer cb = null; try { long size = fc.size(); cb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size); ...do the magic... finally { try { fc.close(); } catch (Exception ex) { } try { fis.close(); } catch (Exception ex) { } closeDirectBuffer(cb); } private void closeDirectBuffer(ByteBuffer cb) { if (cb==null || !cb.isDirect()) return; // we could use this type cast and call functions without reflection code, // but static import from sun.* package is risky for non-SUN virtual machine. //try { ((sun.nio.ch.DirectBuffer)cb).cleaner().clean(); } catch (Exception ex) { } try { Method cleaner = cb.getClass().getMethod("cleaner"); cleaner.setAccessible(true); Method clean = Class.forName("sun.misc.Cleaner").getMethod("clean"); clean.setAccessible(true); clean.invoke(cleaner.invoke(cb)); } catch(Exception ex) { } cb = null; } 

想法来自这些职位。
* 如何使用Java中的FileChannel从映射的内存中取消映射文件?
强制释放本地内存直接使用sun.misc.Unsafe分配ByteBuffer的例子?
* https://github.com/elasticsearch/elasticsearch/blob/master/src/main/java/org/apache/lucene/store/bytebuffer/ByteBufferAllocator.java#L40

sun.misc.Cleaner javadoc说:

通用的幻像参考为基础的清洁工。 清洁剂是一个轻量化和更强大的替代完成。 它们是轻量级的,因为它们不是由VM创build的,因此不需要创buildJNI上调,因为它们的清除代码是由引用处理程序线程而不是由终结器线程直接调用的。 它们更健壮,因为它们使用幻影引用(最弱的参考对象types),从而避免了定稿所固有的令人讨厌的顺序问题。 清洁工跟踪对象对象并封装任意清理代码。 在GC检测到吸尘器所指对象已经变成幻像之后的一段时间,引用处理程序线程将运行清理程序。 清洁工也可以直接调用; 他们是线程安全的,并确保他们至多运行一次thunk。 清洁工不是最终的替代品。 只有在清理代码非常简单直接的情况下才能使用它们。 非平凡的清洁工是不可取的,因为他们冒险阻止了引用处理程序的线程,并推迟进一步的清理和定稿。

运行System.gc()是可接受的解决scheme,如果你的缓冲区总大小很小,但如果我映射千兆字节的文件,我会尝试像这样实现:

 ((DirectBuffer) buffer).cleaner().clean() 

但! 确保清洗后不要访问该缓冲区,否则最终会出现:

Java Runtime Environment检测到一个致命错误:在pc = 0x0000000002bcf700,pid = 7592,tid = 10184 JRE版本:Java™SE运行时环境(8.0_40-b25)(build 1.8.0_40-b25)上检测到EXCEPTION_ACCESS_VIOLATION(0xc0000005) b25)Java VM:Java HotSpot(TM)64位服务器虚拟机(25.40-b25混合模式windows-amd64压缩oops)有问题的帧:J 85 C2 java.nio.DirectByteBuffer.get(I)B(16字节)@ 0x0000000002bcf700 [0x0000000002bcf6c0 + 0x40]写入核心转储失败。 在客户端版本的Windows上,默认情况下未启用小型转储程序包含更多信息的错误报告文件另存为:C:\ Users \ ????? \ Programs \ testApp \ hs_err_pid7592.log编译方法(c2)42392 85 4 java。 nio.DirectByteBuffer :: get(16字节)堆总数[0x0000000002bcf590,0x0000000002bcf828] = 664重定位[0x0000000002bcf6b0,0x0000000002bcf6c0] = 16主代码[0x0000000002bcf6c0,0x0000000002bcf760] = 160存根代码
[0x0000000002bcf760,0x0000000002bcf778] = 24 oops
[0x0000000002bcf778,0x0000000002bcf780] = 8元数据
[0x0000000002bcf780,0x0000000002bcf798] = 24范围数据
[0x0000000002bcf798,0x0000000002bcf7e0] = 72个示波器个
[0x0000000002bcf7e0,0x0000000002bcf820] = 64依赖关系
[0x0000000002bcf820,0x0000000002bcf828] = 8

祝你好运!

要解决Java中的这个错误,我必须执行以下操作,对于中小型文件,这些操作可以正常工作:

  // first open the file for random access RandomAccessFile raf = new RandomAccessFile(file, "r"); // extract a file channel FileChannel channel = raf.getChannel(); // you can memory-map a byte-buffer, but it keeps the file locked //ByteBuffer buf = // channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); // or, since map locks the file... just read the whole file into memory ByteBuffer buf = ByteBuffer.allocate((int)file.length()); int read = channel.read(buf); // .... do something with buf channel.force(false); // doesn't help channel.close(); // doesn't help channel = null; // doesn't help buf = null; // doesn't help raf.close(); // try to make sure that this thing is closed!!!!! 

被映射的内存被使用,直到它被垃圾收集器释放。

从FileChannel文档

映射一旦build立,就不依赖于用来创build它的文件通道。 closures频道,特别是对映射的有效性没有影响。

从MappedByteBuffer java doc

映射的字节缓冲区及其所表示的文件映射在缓冲区本身被垃圾收集之前保持有效。

所以我build议确保没有剩余的映射字节缓冲区的引用,然后请求垃圾回收。

 public static void unMapBuffer(MappedByteBuffer buffer, Class channelClass) { if (buffer == null) { return; } try { Method unmap = channelClass.getDeclaredMethod("unmap", MappedByteBuffer.class); unmap.setAccessible(true); unmap.invoke(channelClass, buffer); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } 

看到如此多的build议去做“有效的Java”中的第7项具体说不要做的事情是很有趣的。 类似@Whome所做的终止方法,不需要引用缓冲区。 GC不能被强制。 但是这并不妨碍开发者尝试。 我发现另一个解决方法是使用http://java.baresovi.cz/dr/en/java#memoryMap中的; WeakReferences

 final MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size); .... final WeakReference<mappedbytebuffer> bufferWeakRef = new WeakReference<mappedbytebuffer>(bb); bb = null; final long startTime = System.currentTimeMillis(); while(null != bufferWeakRef.get()) { if(System.currentTimeMillis() - startTime > 10) // give up return; System.gc(); Thread.yield(); } 

我会尝试JNI:

 #ifdef _WIN32 UnmapViewOfFile(env->GetDirectBufferAddress(buffer)); #else munmap(env->GetDirectBufferAddress(buffer), env->GetDirectBufferCapacity(buffer)); #endif 

包含文件:windows.h Windows,sys / mmap.h,BSD,Linux,OSX。

如果可以保证映射的文件缓冲区对象符合垃圾回收的条件,则不需要GC整个VM来获取释放缓冲区的映射内存。 你可以调用System.runFinalization()。 这将调用映射的文件缓冲区对象的finalize()方法(如果在应用程序线程中没有对它的引用),它将释放映射的内存。

正确的解决scheme是使用try-with-resources。

这样就可以将Channel和其他资源的创build范围限定为一个区块。 一旦该块退出,频道和其他资源不见了,随后不能使用(因为没有任何引用)。

在下一个GC运行之前,内存映射仍然不会被取消,但是至less没有任何悬而未决的引用。