ZhekaKozlov
ZhekaKozlov

Reputation: 39536

How to properly close MappedByteBuffer?

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

Answers (5)

I0n17z474n
I0n17z474n

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

Vlad
Vlad

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

Shunlong Chen
Shunlong Chen

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

ZhekaKozlov
ZhekaKozlov

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

SANN3
SANN3

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

Related Questions