我正在使用文件("sample.txt")映射到内存FileChannel.map()
,然后使用关闭通道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中找不到任何方法).谢谢.
编辑:看起来像(添加一个unmap方法)作为RFE提交给sun一段时间了:http : //bugs.sun.com/view_bug.do?bug_id=4724038
可以使用以下静态方法:
public static void unmap(MappedByteBuffer buffer) { sun.misc.Cleaner cleaner = ((DirectBuffer) buffer).cleaner(); cleaner.clean(); }
但这是不安全的解决方案,原因如下:
1)如果有人在unmap之后使用MappedByteBuffer导致失败
2)它依赖于MappedByteBuffer实现细节
[WinXP,SunJDK1.6]我从filechannel获取了一个映射的ByteBuffer.在阅读了SO帖后,终于设法通过反射呼叫清洁工而没有任何太阳.*包进口.不再存在文件锁定.
编辑添加了JDK9 +代码(Luke Hutchison).
private static 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) { } // JavaSpecVer: 1.6, 1.7, 1.8, 9, 10 boolean isOldJDK = System.getProperty("java.specification.version","99").startsWith("1."); try { if (isOldJDK) { 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)); } else { Class unsafeClass; try { unsafeClass = Class.forName("sun.misc.Unsafe"); } catch(Exception ex) { // jdk.internal.misc.Unsafe doesn't yet have an invokeCleaner() method, // but that method should be added if sun.misc.Unsafe is removed. unsafeClass = Class.forName("jdk.internal.misc.Unsafe"); } Method clean = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class); clean.setAccessible(true); Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe"); theUnsafeField.setAccessible(true); Object theUnsafe = theUnsafeField.get(null); clean.invoke(theUnsafe, cb); } } catch(Exception ex) { } cb = null; }
想法来自这些帖子.
*如何从java中使用FileChannel映射的内存中取消映射文件?
*强制释放本机内存直接ByteBuffer使用sun.misc.Unsafe分配的示例?
*https://github.com/elasticsearch/elasticsearch/blob/master/src/main/java/org/apache/lucene/store/bytebuffer/ByteBufferAllocator.java#L40
来自MappedByteBuffer
javadoc:
映射的字节缓冲区及其表示的文件映射在缓冲区本身被垃圾收集之前保持有效.
试着打电话System.gc()
?即使这只是对VM的建议.
sun.misc.Cleaner javadoc说:
通用的基于幻像参考的清洁剂.清洁剂是最终确定的轻量级和更强大的替代品.它们是轻量级的,因为它们不是由VM创建的,因此不需要创建JNI上行调用,并且因为它们的清理代码是由引用处理程序线程而不是终结器线程直接调用的.它们更强大,因为它们使用幻像引用,最弱的参考对象类型,从而避免了最终确定的令人讨厌的排序问题.清理器跟踪引用对象并封装任意清理代码.在GC检测到清洁器的指示对象已变为幻像可达之后的一段时间,引用处理程序线程将运行清除程序.清洁工也可以直接调用; 它们是线程安全的,并确保它们最多运行一次.清洁工不是最终确定的替代品.只有在清理代码非常简单和直接时才应该使用它们.非平凡的清洁工是不可取的,因为它们有可能阻塞参考处理程序线程并延迟进一步的清理和最终化.
如果你的缓冲区总大小很小,运行System.gc()是可以接受的解决方案,但如果我映射了几千兆字节的文件,我会尝试这样实现:
((DirectBuffer) buffer).cleaner().clean()
但!确保在清洁后没有访问该缓冲区,或者最终会:
Java运行时环境检测到致命错误:pc = 0x0000000002bcf700处的EXCEPTION_ACCESS_VIOLATION(0xc0000005),pid = 7592,tid = 10184 JRE版本:Java(TM)SE运行时环境(8.0_40-b25)(版本1.8.0_40- b25)Java VM:Java HotSpot(TM)64位服务器VM(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,
[0x0000000002bcf760,0x0000000002bcf778] = 24个糟糕
[0x0000000002bcf778,0x0000000002bcf780] = 8元数据
[0x0000000002bcf780,0x0000000002bcf798] = 24米范围数据
[0x0000000002bcf798,0x0000000002bcf7e0] = 72米范围件
[0x0000000002bcf7e0,0x0000000002bcf820] = 64依赖性
[0x0000000002bcf820,0x0000000002bcf828] = 8
祝好运!