Как удалить файл из памяти, отображенной с помощью FileChannel в Java? - PullRequest
39 голосов
/ 04 июня 2010

Я отображаю файл ("sample.txt") в память, используя FileChannel.map(), а затем закрываю канал, используя fc.close(). После этого, когда я пишу в файл с помощью FileOutputStream, я получаю следующую ошибку:

java.io.FileNotFoundException: sample.txt (Запрошенная операция не может быть выполнено для файла с пользовательский раздел открыт) * ​​1006 *

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

Полагаю, это может быть связано с тем, что файл все еще отображается в памяти даже после закрытия FileChannel. Я прав?. Если так, как я могу «удалить» файл из памяти? (Я не могу найти какие-либо методы для этого в API). Спасибо.

Edit: Похоже, это (добавление метода unmap) было представлено как RFE для Sun некоторое время назад: http://bugs.sun.com/view_bug.do?bug_id=4724038

Ответы [ 12 ]

37 голосов
/ 18 февраля 2011

Можно использовать следующий статический метод:

public static void unmap(MappedByteBuffer buffer)
{
   sun.misc.Cleaner cleaner = ((DirectBuffer) buffer).cleaner();
   cleaner.clean();
}

Но это небезопасное решение из-за следующего:
1) Приводит к сбоям, если кто-то использует MappedByteBuffer после unmap
2) Он основан на деталях реализации MappedByteBuffer

17 голосов
/ 18 октября 2013

[WinXP, SunJDK1.6] У меня был сопоставленный ByteBuffer, взятый из файлового канала. После прочтения SO сообщений, наконец, удалось вызвать уборщика через отражение без какого-либо импорта пакета sun. *. Больше нет блокировки файлов.

edit Добавлен код JDK9 + (Люк Хатчисон).

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;
}

Идеи были взяты из этих сообщений.
* Как удалить файл из памяти, отображенной с помощью FileChannel в Java?
* Примеры принудительного освобождения собственной памяти, выделенной прямым байт-буфером с использованием sun.misc.Unsafe?
* https://github.com/elasticsearch/elasticsearch/blob/master/src/main/java/org/apache/lucene/store/bytebuffer/ByteBufferAllocator.java#L40

10 голосов
/ 04 июня 2010

Из MappedByteBuffer Javadoc:

Отображенный байтовый буфер и отображаемое им отображение файла остаются действительными до тех пор, пока сам буфер не будет собран мусором.

Попробуйте позвонить System.gc()? Даже это только предложение для ВМ.

5 голосов
/ 23 июля 2015

sun.misc.Cleaner Javadoc говорит:

Универсальные чистящие средства на основе фантомных эталонов. Очистители - это легкая и более надежная альтернатива доработке. Они легковесны, потому что они не созданы виртуальной машиной и, следовательно, не требуют создания вызова JNI, а также потому, что их код очистки вызывается непосредственно потоком обработчика ссылок, а не потоком финализатора. Они более устойчивы, потому что они используют фантомные ссылки, самый слабый тип ссылочного объекта, тем самым избегая неприятных проблем упорядочения, присущих финализации. Очиститель отслеживает референтный объект и инкапсулирует множество произвольного кода очистки. Через некоторое время после того, как сборщик мусора обнаружит, что референт очистителя стал фантомно достижимым, поток обработчика ссылок запустит очиститель. Очистители также могут быть вызваны напрямую; они безопасны для потока и гарантируют, что они запускают свои блоки не более одного раза. Очистители не являются заменой для доработки. Их следует использовать только тогда, когда код очистки предельно прост и понятен. Нетривиальные очистители нецелесообразны, поскольку они рискуют заблокировать поток обработчика ссылок и отложить дальнейшую очистку и завершение.

Запуск System.gc () является приемлемым решением, если общий размер ваших буферов невелик, но если бы я отображал гигабайты файлов, я бы попытался реализовать так:

((DirectBuffer) buffer).cleaner().clean()

Но! Убедитесь, что у вас нет доступа к этому буферу после очистки, иначе вы получите:

Неустранимая ошибка была обнаружена средой выполнения Java: EXCEPTION_ACCESS_VIOLATION (0xc0000005) при pc = 0x0000000002bcf700, pid = 7592, tid = 10184 Версия JRE: среда выполнения Java (TM) SE (8.0_40-b25) (сборка 1.8.0_40-b25) Java VM: 64-разрядная версия Java HotSpot (TM) Виртуальная машина сервера (25.40-b25, смешанный режим, windows-amd64, сжатый упс) Проблемный кадр: J 85 C2 java.nio.DirectByteBuffer.get (I) B (16) байт) @ 0x0000000002bcf700 [0x0000000002bcf6c0 + 0x40] Не удалось записать основной дамп. Мини-дампы не включены по умолчанию в клиентских версиях Windows Файл отчета об ошибке с дополнительной информацией сохраняется как: C: \ Users \ ????? \ Programs \ testApp \ hs_err_pid7592.log Скомпилированный метод (c2) 42392 85 4 java.nio.DirectByteBuffer :: get (16 байт) всего в куче [0x0000000002bcf590,0x0000000002bcf828] = 664 перемещение [0x0000000002bcf6b0,0x0000000002bcf6c0] = 16 основной код [0x0000000002bcf6c0,0x0000000002bcf760] = код заглушки 160
[0x0000000002bcf760,0x0000000002bcf778] = 24 упа
[0x0000000002bcf778,0x0000000002bcf780] = 8 метаданных
[0x0000000002bcf780,0x0000000002bcf798] = 24 данных области
[0x0000000002bcf798,0x0000000002bcf7e0] = 72 шт. Шт.
[0x0000000002bcf7e0,0x0000000002bcf820] = 64 зависимости
[0x0000000002bcf820,0x0000000002bcf828] = 8

Удачи!

2 голосов
/ 19 января 2012

Чтобы обойти эту ошибку в Java, мне нужно было сделать следующее, что будет нормально работать с файлами малого и среднего размера:

    // first open the file for random access
    RandomAccessFile raf = new RandomAccessFile(file, "r");

    // extract a file channel
    FileChannel channel = raf.getChannel();

    // you can memory-map a byte-buffer, but it keeps the file locked
    //ByteBuffer buf =
    //        channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());

    // or, since map locks the file... just read the whole file into memory
    ByteBuffer buf = ByteBuffer.allocate((int)file.length());
    int read = channel.read(buf);

    // .... do something with buf

    channel.force(false);  // doesn't help
    channel.close();       // doesn't help
    channel = null;        // doesn't help
    buf = null;            // doesn't help
    raf.close();           // try to make sure that this thing is closed!!!!!
1 голос
/ 05 января 2019

Метод, описанный в других ответах, в котором используется ((DirectBuffer) byteBuffer).cleaner().clean(), не работает с JDK 9+ (даже в отражающей форме) без отображения предупреждения An illegal reflective access operation has occurred. Это перестанет работать вообще в некоторых будущих версиях JDK. К счастью, sun.misc.Unsafe.invokeCleaner(ByteBuffer) может сделать тот же самый вызов для вас без предупреждения: (из источника OpenJDK 11):

public void invokeCleaner(java.nio.ByteBuffer directBuffer) {
    if (!directBuffer.isDirect())
        throw new IllegalArgumentException("buffer is non-direct");

    DirectBuffer db = (DirectBuffer)directBuffer;
    if (db.attachment() != null)
        throw new IllegalArgumentException("duplicate or slice");

    Cleaner cleaner = db.cleaner();
    if (cleaner != null) {
        cleaner.clean();
    }
}

Будучи sun.misc классом, он будет удален в какой-то момент. Интересно, что все вызовы, кроме этого в sun.misc.Unsafe, проксируются напрямую на jdk.internal.misc.Unsafe (я не знаю, почему invokeCleaner(ByteBuffer) не проксируется так же, как и все другие методы - это, вероятно, было случайное упущение).

Я написал следующий код, который может очищать / закрывать / отменять отображение экземпляров DirectByteBuffer / MappedByteBuffer на JDK 7/8, а также на JDK 9+, и это не выдает предупреждение об отражении:

private static boolean PRE_JAVA_9 = 
        System.getProperty("java.specification.version","9").startsWith("1.");

private static Method cleanMethod;
private static Method attachmentMethod;
private static Object theUnsafe;

static void getCleanMethodPrivileged() {
    if (PRE_JAVA_9) {
        try {
            cleanMethod = Class.forName("sun.misc.Cleaner").getMethod("clean");
            cleanMethod.setAccessible(true);
            final Class<?> directByteBufferClass =
                    Class.forName("sun.nio.ch.DirectBuffer");
            attachmentMethod = directByteBufferClass.getMethod("attachment");
            attachmentMethod.setAccessible(true);
        } catch (final Exception ex) {
        }
    } else {
        try {
            Class<?> unsafeClass;
            try {
                unsafeClass = Class.forName("sun.misc.Unsafe");
            } catch (Exception e) {
                // jdk.internal.misc.Unsafe doesn't yet have invokeCleaner(),
                // but that method should be added if sun.misc.Unsafe is removed.
                unsafeClass = Class.forName("jdk.internal.misc.Unsafe");
            }
            cleanMethod = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class);
            cleanMethod.setAccessible(true);
            final Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
            theUnsafeField.setAccessible(true);
            theUnsafe = theUnsafeField.get(null);
        } catch (final Exception ex) {
        }
    }
}

static {
    AccessController.doPrivileged(new PrivilegedAction<Object>() {
        @Override
        public Object run() {
            getCleanMethodPrivileged();
            return null;
        }
    });
}

private static boolean closeDirectByteBufferPrivileged(
            final ByteBuffer byteBuffer, final LogNode log) {
    try {
        if (cleanMethod == null) {
            if (log != null) {
                log.log("Could not unmap ByteBuffer, cleanMethod == null");
            }
            return false;
        }
        if (PRE_JAVA_9) {
            if (attachmentMethod == null) {
                if (log != null) {
                    log.log("Could not unmap ByteBuffer, attachmentMethod == null");
                }
                return false;
            }
            // Make sure duplicates and slices are not cleaned, since this can result in
            // duplicate attempts to clean the same buffer, which trigger a crash with:
            // "A fatal error has been detected by the Java Runtime Environment:
            // EXCEPTION_ACCESS_VIOLATION"
            // See: https://stackoverflow.com/a/31592947/3950982
            if (attachmentMethod.invoke(byteBuffer) != null) {
                // Buffer is a duplicate or slice
                return false;
            }
            // Invoke ((DirectBuffer) byteBuffer).cleaner().clean()
            final Method cleaner = byteBuffer.getClass().getMethod("cleaner");
            cleaner.setAccessible(true);
            cleanMethod.invoke(cleaner.invoke(byteBuffer));
            return true;
        } else {
            if (theUnsafe == null) {
                if (log != null) {
                    log.log("Could not unmap ByteBuffer, theUnsafe == null");
                }
                return false;
            }
            // In JDK9+, calling the above code gives a reflection warning on stderr,
            // need to call Unsafe.theUnsafe.invokeCleaner(byteBuffer) , which makes
            // the same call, but does not print the reflection warning.
            try {
                cleanMethod.invoke(theUnsafe, byteBuffer);
                return true;
            } catch (final IllegalArgumentException e) {
                // Buffer is a duplicate or slice
                return false;
            }
        }
    } catch (final Exception e) {
        if (log != null) {
            log.log("Could not unmap ByteBuffer: " + e);
        }
        return false;
    }
}

/**
 * Close a {@code DirectByteBuffer} -- in particular, will unmap a
 * {@link MappedByteBuffer}.
 * 
 * @param byteBuffer
 *            The {@link ByteBuffer} to close/unmap.
 * @param log
 *            The log.
 * @return True if the byteBuffer was closed/unmapped (or if the ByteBuffer
 *            was null or non-direct).
 */
public static boolean closeDirectByteBuffer(final ByteBuffer byteBuffer,
            final Log log) {
    if (byteBuffer != null && byteBuffer.isDirect()) {
        return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
            @Override
            public Boolean run() {
                return closeDirectByteBufferPrivileged(byteBuffer, log);
            }
        });
    } else {
        // Nothing to unmap
        return false;
    }
}

Обратите внимание, что вам нужно будет добавить requires jdk.unsupported в дескриптор вашего модуля в модульной среде выполнения на JDK 9+ (необходим для использования Unsafe).

Ваша банка может также потребовать RuntimePermission("accessClassInPackage.sun.misc"), RuntimePermission("accessClassInPackage.jdk.internal.misc") и ReflectPermission("suppressAccessChecks").

1 голос
/ 09 мая 2017

Я узнал информацию о unmap, это метод FileChannelImpl, и он недоступен, так что вы можете вызвать его с помощью java отражения, например:

public static void unMapBuffer(MappedByteBuffer buffer, Class channelClass) {
    if (buffer == null) {
        return;
    }

    try {
        Method unmap = channelClass.getDeclaredMethod("unmap", MappedByteBuffer.class);
        unmap.setAccessible(true);
        unmap.invoke(channelClass, buffer);
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
}
1 голос
/ 04 июня 2010

Отображенная память используется до тех пор, пока она не будет освобождена сборщиком мусора.

Из FileChannel Документы

Отображение, однажды созданное, не зависит от файлового канала, который использовался для его создания. В частности, закрытие канала не влияет на достоверность сопоставления.

Из MappedByteBuffer Java-документ

Отображенный байтовый буфер и сопоставление файлов, которое он представляет, остаются действительными до тех пор, пока сам буфер не будет удален.

Таким образом, я хотел бы предложить убедиться, что не осталось никаких ссылок на сопоставленный байтовый буфер, а затем запросить сборку мусора.

0 голосов
/ 12 октября 2017

Я бы попробовал JNI:

#ifdef _WIN32
UnmapViewOfFile(env->GetDirectBufferAddress(buffer));
#else
munmap(env->GetDirectBufferAddress(buffer), env->GetDirectBufferCapacity(buffer));
#endif

Включите файлы: windows.h для Windows, sys / mmap.h для BSD, Linux, OSX.

0 голосов
/ 24 августа 2017

Забавно видеть так много рекомендаций, чтобы делать то, что пункт 7 в «Эффективной Java» определенно запрещает делать. Необходим метод завершения, такой как @Whome, и отсутствие ссылок на буфер. GC не может быть принудительным. Но это не останавливает разработчиков от попыток. Другой обходной путь, который я нашел, заключался в использовании WeakReferences из http://jan.baresovi.cz/dr/en/java#memoryMap

final MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size);
....
final WeakReference<mappedbytebuffer> bufferWeakRef = new WeakReference<mappedbytebuffer>(bb);
bb = null;

final long startTime = System.currentTimeMillis();
while(null != bufferWeakRef.get()) {
  if(System.currentTimeMillis() - startTime > 10)
// give up
    return;
    System.gc();
    Thread.yield();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...