How to unmap a file from memory mapped using FileChannel in java?
I am mapping a file("sample.txt") to memory using FileChannel.map()
and then closing the channel using fc.close()
. After this when I write to the file using FileOutputStream, I am getting the following error:
java.io.FileNotFoundException: sample.txt (The requested operation cannot be per formed on a file with a user-mapped section open)
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();
I presume this may be due to file being still mapped to the memory even after I close the FileChannel
. Am I right?. If so, how can I "unmap" the file from memory?(I can't find any methods for this in the API).
Thanks.
Edit: Looks like it(adding an unmap method) was submitted as RFE to sun some time back: http://bugs.sun.com/view_bug.do?bug_id=4724038
Following static method could be used:
public static void unmap(MappedByteBuffer buffer)
{
sun.misc.Cleaner cleaner = ((DirectBuffer) buffer).cleaner();
cleaner.clean();
}
But this is unsafe solution because of following:
1) Lead to failures if someone use MappedByteBuffer after unmap
2) It relies on MappedByteBuffer implementation details
[WinXP,SunJDK1.6] I had a mapped ByteBuffer taken from filechannel. After reading SO posts finally managed to call a cleaner through reflection without any sun.* package imports. No longer file lock is lingering.
edit Added JDK9+ code(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;
}
Ideas were taken from these posts.
* How to unmap a file from memory mapped using FileChannel in java?
* Examples of forcing freeing of native memory direct ByteBuffer has allocated, using sun.misc.Unsafe?
* https://github.com/elasticsearch/elasticsearch/blob/master/src/main/java/org/apache/lucene/store/bytebuffer/ByteBufferAllocator.java#L40
From the MappedByteBuffer
javadoc:
A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself is garbage-collected.
Try calling System.gc()
? Even that's only a suggestion to the VM.