¿Cómo desmapear un archivo de memoria mapeada usando FileChannel en java?


Estoy mapeando un archivo ("sample.txt") a la memoria usando FileChannel.map() y luego cerrando el canal usando fc.close(). Después de esto, cuando escribo en el archivo usando FileOutputStream, recibo el siguiente error:

Java. io. FileNotFoundException: muestra.txt (La operación solicitada no se puede formar en un archivo con un sección mapeada por el usuario abierta)

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

Supongo que esto puede deberse a que el archivo aún está asignado a la memoria incluso después de cerrar el FileChannel. ¿Tengo razón?. Si es así, ¿cómo puedo "desmapear" el archivo de la memoria?(No puedo encontrar ningún método para esto en la API). Gracias.

Editar: Parece que (agregando un método unmap) fue enviado como RFE a sun hace algún tiempo: http://bugs.sun.com/view_bug.do?bug_id=4724038

Author: learner135, 2010-06-04

11 answers

Del MappedByteBuffer javadoc:

Un búfer de bytes mapeado y la asignación de archivos que representa siguen siendo válidos hasta que el búfer en sí mismo se recolecta como basura.

Intenta llamar a System.gc()? Incluso eso es solo una sugerencia para la VM.

 7
Author: scompt.com,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-04 09:58:13

Se podría utilizar el siguiente método estático:

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

Pero esta es una solución insegura debido a lo siguiente:
1) Conducir a fallas si alguien usa MappedByteBuffer después de unmap
2) Se basa en MappedByteBuffer detalles de implementación

 36
Author: Timur Yusupov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-24 20:31:47

[WinXP,SunJDK1.6] Tuve un ByteBuffer mapeado tomado de filechannel. Después de leer por lo que los mensajes finalmente logró llamar a un limpiador a través de la reflexión sin ningún tipo de sol.* importación de paquetes. Ya no hay bloqueo de archivos.

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

Las ideas fueron tomadas de estos posts.
* ¿Cómo desmapear un archivo de memoria mapeada usando FileChannel en java?
* Ejemplos de forzar la liberación de memoria nativa direct ByteBuffer ha asignado, usando sun.misc.Inseguro?
* https://github.com/elasticsearch/elasticsearch/blob/master/src/main/java/org/apache/lucene/store/bytebuffer/ByteBufferAllocator.java#L40

 17
Author: Whome,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:44

Sun.misc.El limpiador javadoc dice:

Limpiadores de uso general basados en referencia fantasma. Los limpiadores son una alternativa ligera y más robusta a la finalización. Son livianos porque no son creados por la VM y por lo tanto no requieren que se cree una llamada upcall JNI, y porque su código de limpieza es invocado directamente por el subproceso reference-handler en lugar de por el subproceso finalizer. Son más robustos porque utilizan referencias fantasma, el tipo más débil de objeto de referencia, evitando así los desagradables problemas de orden inherentes a la finalización. Un limpiador rastrea un objeto de referencia y encapsula un código de limpieza arbitrario. Algún tiempo después de que el GC detecte que el referente de un limpiador se ha vuelto phantom-reachable, el hilo reference-handler ejecutará el limpiador. Los limpiadores también se pueden invocar directamente; son seguros para el hilo y se aseguran de que ejecuten sus thunks a lo sumo una vez. Los limpiadores no son un reemplazo para la finalización. Deberían ser se usa solo cuando el código de limpieza es extremadamente simple y directo. Los limpiadores no triviales son desaconsejables ya que corren el riesgo de bloquear el subproceso del controlador de referencia y retrasar la limpieza y finalización.

Sistema en ejecución.gc() es una solución aceptable si el tamaño total de sus búferes es pequeño, pero si estuviera mapeando gigabytes de archivos, intentaría implementarlo de la siguiente manera:

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

Pero! Asegúrese de no acceder a ese búfer después de la limpieza o terminará con:

El entorno de ejecución de Java ha detectado un error fatal: EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x0000000002bcf700, pid = 7592, tid=10184 JRE version: Java (TM) SE Runtime Environment (8.0_40-b25) (build 1.8.0_40-b25) Java VM: Java HotSpot (TM) 64-Bit Servidor VM (25.40-b25 modo mixto windows-amd64 comprimido oops) Marco problemático: J 85 C2 java.nio.DirectByteBuffer.get (I) B (16 bytes) @ 0x00000000002bcf700 [0x0000000002bcf6c0+0x40] Fallo al escribir volcado de núcleo. Los minidumps no están habilitados de forma predeterminada en las versiones de cliente de Windows Un archivo de informe de error con más información se guarda como: C:\Users\?????\Programs \ TestApp \ hs_err_pid7592.método compilado de registro (c2) 42392 85 4 java.nio.DirectByteBuffer:: get (16 bytes) total en montón [0x0000000002bcf590, 0x0000000002bcf828] = 664 relocation [0x0000000002bcf6b0, 0x0000000002bcf6c0] = 16 código principal [0x0000000002bcf6c0, 0x0000000002bcf760] = 160 código de talón
[0x0000000002bcf760, 0x0000000002bcf778] = 24 oops
[0x0000000002bcf778,0x0000000002bcf780] = 8 metadatos
[0x0000000002bcf780, 0x0000000002bcf798] = 24 datos de alcance
[0x0000000002bcf798, 0x0000000002bcf7e0] = 72 alcances pcs
[0x0000000002bcf7e0, 0x0000000002bcf820] = 64 dependencias
[0x0000000002bcf820, 0x0000000002bcf828] = 8

¡Buena suerte!

 5
Author: Dmitrius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-23 16:21:54

Para solucionar este error en Java, tuve que hacer lo siguiente, que funcionará bien para archivos pequeños y medianos:

    // 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!!!!!
 2
Author: Mr Ed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-19 09:22:15

La memoria asignada se usa hasta que es liberada por el recolector de basura.

De FileChannel docs

Una asignación, una vez establecida, no depende del canal de archivo que se utilizó para crearla. El cierre del canal, en particular, no tiene ningún efecto sobre la validez de la asignación.

De MappedByteBuffer java doc

Un búfer de bytes mapeado y la asignación de archivos que representa siguen siendo válidos hasta que el búfer recogida de basura.

Así que sugeriría asegurar que no haya referencias restantes al búfer de bytes mapeado y luego solicitar una recolección de basura.

 1
Author: BenM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-04 09:59:06

Encontré información sobre unmap, es un método de FileChannelImpl y no accesible, por lo que puede invocarlo por java reflect como:

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
Author: 饒夢楠,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-23 14:05:22

Es divertido ver tantas recomendaciones para hacer lo que el Ítem 7 en 'Java Efectivo' dice específicamente que no hacer. Un método de terminación como lo que hizo @Whome y sin referencias al búfer es lo que se necesita. GC no puede ser forzado. Pero eso no impide que los desarrolladores lo intenten. Otra solución que encontré fue usar WeakReferences de 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();
}
 0
Author: Droid Teahouse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-23 23:50:35

Probaría JNI:

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

Incluye archivos: windows.h para Windows, sys / mmap.h para BSD, Linux, OSX.

 0
Author: martins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-12 06:39:05

Si se puede garantizar que el objeto de búfer de archivo mapeado sea elegible para la recolección de elementos no utilizados, no es necesario que GC toda la VM para que se libere la memoria mapeada del búfer. Puedes llamar al Sistema.runFinalization() . Esto llamará al método finalize () en el objeto de búfer de archivo asignado (si no hay referencias a él en los subprocesos de su aplicación) que liberará la memoria asignada.

 -2
Author: somedude,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-01 11:44:56

La solución correcta aquí es usar try-with-resources.

Esto permite la creación del Canal y los otros recursos para ser scoped a un bloque. Una vez que el bloque sale, el Canal y otros recursos desaparecen y posteriormente no se pueden usar (ya que nada tiene una referencia a ellos).

La asignación de memoria todavía no se deshará hasta la próxima ejecución de GC, pero al menos no hay ninguna referencia pendiente a ella.

 -12
Author: kittylyst,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-05 14:56:04