Подтвердить что ты не робот

Как распаковать файл из памяти, отображаемой с помощью FileChannel в java?

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

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

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). Спасибо.

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

4b9b3361

Ответ 1

Из MappedByteBuffer javadoc:

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

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

Ответ 2

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

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

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

Ответ 3

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

FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteBuffer cb = null;
try {
    long size = fc.size();
    cb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size);
    ...do the magic...
finally {
    try { fc.close(); } catch (Exception ex) { }
    try { fis.close(); } catch (Exception ex) { }
    closeDirectBuffer(cb);
}

private 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) { }
    try {
        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));
    } catch(Exception ex) { }
    cb = null;
}

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

Ответ 4

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

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

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

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

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

Неустойчивая ошибка была обнаружена средой Java Runtime Environment: EXCEPTION_ACCESS_VIOLATION (0xc0000005) при pc = 0x0000000002bcf700, pid = 7592, tid = 10184 JRE версия: Java (TM) SE Runtime Environment (8.0_40-b25) (build 1.8.0_40-b25) Java VM: Java HotSpot (TM) 64-разрядный Сервер VM (25.40-b25 смешанный режим windows-amd64 сжат oops) Проблемная структура: J 85 C2 java.nio.DirectByteBuffer.get(I) B (16 байты) @0x0000000002bcf700 [0x0000000002bcf6c0 + 0x40] Не удалось написать ядро. Minidumps по умолчанию не включены в клиентских версиях 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 oops
[0x0000000002bcf778,0x0000000002bcf780] = 8 метаданных
[0x0000000002bcf780,0x0000000002bcf798] = 24 области данных

[0x0000000002bcf798,0x0000000002bcf7e0] = 72 шт шт.

[0x0000000002bcf7e0,0x0000000002bcf820] = 64 зависимости
[0x0000000002bcf820,0x0000000002bcf828] = 8

Удачи!

Ответ 5

Чтобы обойти эту ошибку на 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!!!!!

Ответ 6

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

Из FileChannel docs

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

От MappedByteBuffer java doc

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

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

Ответ 7

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

Ответ 8

Забавно видеть так много рекомендаций, чтобы сделать то, что конкретно указывает пункт 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();
}

Ответ 9

Я бы попробовал 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.

Ответ 10

Если объект сопоставленного файлового буфера может быть гарантирован для сбора мусора, вам не нужно объединять всю виртуальную машину, чтобы получить выпущенную карту памяти. Вы можете вызвать System.runFinalization(). Это вызовет метод finalize() для объекта сопоставленного файлового буфера (если в потоках вашего приложения нет ссылок на него), который освободит отображаемую память.

Ответ 11

Правильное решение здесь - использовать try-with-resources.

Это позволяет создать канал и другие ресурсы для привязки к блоку. Как только блок выйдет, канал и другие ресурсы исчезнут, а затем не могут использоваться (поскольку ничто не ссылается на них).

Отображение памяти по-прежнему не будет отменено до тех пор, пока не будет запущен следующий GC, но, по крайней мере, нет ссылок на него.