How to properly close MappedByteBuffer?

5.8k views Asked by At

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?

4

There are 4 answers

2
SANN3 On BEST ANSWER

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");
    }
}
0
Vlad On

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.

0
Shunlong Chen On

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;
    });
}
8
ZhekaKozlov On

@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);