Reputation: 39536
This is the code I'm running:
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class Main {
public static void main(String[] args) throws Exception {
String filePath = "D:/temp/file";
RandomAccessFile file = new RandomAccessFile(filePath, "rw");
try {
MappedByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, 128);
// Do something
buffer.putInt(4);
} finally {
file.close();
System.out.println("File closed");
}
System.out.println("Press any key...");
System.in.read();
System.out.println("Finished");
}
}
Before pressing a key, I'm trying to delete the file manually in FAR Manager. But FAR says that the file is locked:
The process cannot access the file because it is being used by another process.
Cannot delete the file
D:\temp\file
Object is being opened in:
Java(TM) Platform SE binary (PID: 5768, C:\Program Files\Java\jdk1.8.0_05\bin\javaw.exe)
Only after pressing a key, the application terminates and I can delete the file.
What is wrong with my code?
Upvotes: 9
Views: 6452
Reputation: 1
another option is like this:
import jdk.internal.access.JavaNioAccess;
import jdk.internal.access.SharedSecrets;
...
JavaNioAccess nioAccess = SharedSecrets.getJavaNioAccess();
nioAccess.unmapper(yourBiteBuffer).unmap();
you do need to export to your module, the public members of 2 packages from java.base internal module in order to make it work (at build time and runtime):
--add-exports java.base/jdk.internal.access=ALL-UNNAMED --add-exports java.base/jdk.internal.access.foreign=ALL-UNNAMED
you can see it being used this way in the OpenJdk implementation https://github.com/openjdk/jdk22/blob/master/src/java.base/share/classes/java/nio/file/FileChannelLinesSpliterator.java#L279-L291
Upvotes: 0
Reputation: 315
This is actually a limitation of JDK. Since the JDK-4724038 which tracks this problem (even though it is marked an enhancement) in JDK says that invoking the cleanup method directly is strongly advised against (also, that the Unsafe
class might go away in some future version of JDK), the only workaround seems to be to call the GC. If using the try-with-resources for the file, that would look like this:
try (RandomAccessFile file = new RandomAccessFile(filePath, "rw")) {
MappedByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, 128);
// Do something
buffer.putInt(4);
}
System.gc(); // has to be called outside the try-with-resources block
I created https://github.com/vladak/RandomAccessFileTrap to demonstrate this - take a look at the detail of a build in the Github actions tab for this repository to see the actual results.
Upvotes: 1
Reputation: 23
If you are using java1.8 and cannot directly use sun.nio.ch.DirectBuffer and Cleaner, you can try:
public void clean(final ByteBuffer buffer) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
try {
Field field = buffer.getClass().getDeclaredField("cleaner");
field.setAccessible(true);
Object cleaner = field.get(buffer);
Method cleanMethod = cleaner.getClass().getMethod("clean");
cleanMethod.invoke(cleaner);
} catch (Exception e) {
e.printStackTrace();
}
return null;
});
}
Upvotes: 1
Reputation: 39536
@SANN3's answer doesn't work on Java 9 anymore. In Java 9 there is a new method sun.misc.Unsafe.invokeCleaner
that can be used. Here is a working code:
MappedByteBuffer buffer = ...
// Java 9+ only:
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Object unsafe = unsafeField.get(null);
Method invokeCleaner = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class);
invokeCleaner.invoke(unsafe, buffer);
Upvotes: 8
Reputation: 10069
Try this one.
public class Test
{
public static void main(String[] args) throws Exception {
String filePath = "D:/temp/file";
RandomAccessFile file = new RandomAccessFile(filePath, "rw");
FileChannel chan = file.getChannel();
try {
MappedByteBuffer buffer = chan.map(FileChannel.MapMode.READ_WRITE, 0, 128);
// Do something
buffer.putInt(4);
buffer.force();
Cleaner cleaner = ((sun.nio.ch.DirectBuffer) buffer).cleaner();
if (cleaner != null) {
cleaner.clean();
}
} finally {
chan.close();
file.close();
System.out.println("File closed");
}
System.out.println("Press any key...");
System.in.read();
System.out.println("Finished");
}
}
Upvotes: 8