How to unmap a file from memory mapped using FileChannel in java?
sun.misc.Cleaner javadoc says:
General-purpose phantom-reference-based cleaners. Cleaners are a lightweight and more robust alternative to finalization. They are lightweight because they are not created by the VM and thus do not require a JNI upcall to be created, and because their cleanup code is invoked directly by the reference-handler thread rather than by the finalizer thread. They are more robust because they use phantom references, the weakest type of reference object, thereby avoiding the nasty ordering problems inherent to finalization. A cleaner tracks a referent object and encapsulates a thunk of arbitrary cleanup code. Some time after the GC detects that a cleaner's referent has become phantom-reachable, the reference-handler thread will run the cleaner. Cleaners may also be invoked directly; they are thread safe and ensure that they run their thunks at most once. Cleaners are not a replacement for finalization. They should be used only when the cleanup code is extremely simple and straightforward. Nontrivial cleaners are inadvisable since they risk blocking the reference-handler thread and delaying further cleanup and finalization.
Running System.gc() is acceptable solution if your buffers total size is small, but if I was mapping gigabytes of files I would try to implement like this:
((DirectBuffer) buffer).cleaner().clean()
But! Make sure you don't access that buffer after cleaning or you will end up with:
A fatal error has been detected by the Java Runtime Environment: EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x0000000002bcf700, pid=7592, tid=10184 JRE version: Java(TM) SE Runtime Environment (8.0_40-b25) (build 1.8.0_40-b25) Java VM: Java HotSpot(TM) 64-Bit Server VM (25.40-b25 mixed mode windows-amd64 compressed oops) Problematic frame: J 85 C2 java.nio.DirectByteBuffer.get(I)B (16 bytes) @ 0x0000000002bcf700 [0x0000000002bcf6c0+0x40] Failed to write core dump. Minidumps are not enabled by default on client versions of Windows An error report file with more information is saved as: C:\Users\?????\Programs\testApp\hs_err_pid7592.log Compiled method (c2) 42392 85 4 java.nio.DirectByteBuffer::get (16 bytes) total in heap [0x0000000002bcf590,0x0000000002bcf828] = 664 relocation [0x0000000002bcf6b0,0x0000000002bcf6c0] = 16 main code [0x0000000002bcf6c0,0x0000000002bcf760] = 160 stub code
[0x0000000002bcf760,0x0000000002bcf778] = 24 oops
[0x0000000002bcf778,0x0000000002bcf780] = 8 metadata
[0x0000000002bcf780,0x0000000002bcf798] = 24 scopes data
[0x0000000002bcf798,0x0000000002bcf7e0] = 72 scopes pcs
[0x0000000002bcf7e0,0x0000000002bcf820] = 64 dependencies
[0x0000000002bcf820,0x0000000002bcf828] = 8
Good luck!
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
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.
[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