JNA/ByteBuffer no liberada y causando C montón de ejecutar fuera de la memoria

StackOverflow https://stackoverflow.com/questions/1744533

  •  20-09-2019
  •  | 
  •  

Pregunta

Permítanme comenzar diciendo que mi comprensión de cómo JNA y Java directa nativo de las asignaciones de memoria es visceral, en el mejor, así que estoy tratando de describir mi comprensión de lo que está pasando.Todas las correcciones, además de las respuestas sería genial...

Estoy ejecutando una aplicación que mezcla Java y C código nativo utilizando JNA y estoy corriendo a través de un reproducible problema con el Java Recolector de Basura no libre de referencias directas nativo de las asignaciones de memoria, lo que resulta en la C montón de ejecutar fuera de la memoria.

Estoy seguro de que mi aplicación C no es la fuente del problema de asignación, ya que estoy pasando un java.nio.ByteBuffer en mi código C, modificar el buffer, y luego acceder a el resultado en mi Java función.Tengo una sola malloc y una sola correspondiente free durante cada llamada a la función, pero después de que en repetidas ocasiones la ejecución del código en Java la malloc eventualmente fallará.

Aquí un poco trivializado conjunto de código que muestra el problema -- realista, estoy tratando de asignar alrededor de 16-32 MB en la C montón durante la llamada a la función.

Mi código de Java hace algo como:

public class MyClass{
    public void myfunction(){
        ByteBuffer foo = ByteBuffer.allocateDirect(1000000);
        MyDirectAccessLib.someOp(foo, 1000000);
        System.out.println(foo.get(0));
    }
}

public MyDirectAccessLib{
    static {
        Native.register("libsomelibrary");
    }
    public static native void someOp(ByteBuffer buf, int size);
}

Entonces mi C código podría ser algo como:

#include <stdio.h>
#include <stdlib.h>
void someOp(unsigned char* buf, int size){
    unsigned char *foo;
    foo = malloc(1000000);
    if(!foo){
        fprintf(stderr, "Failed to malloc 1000000 bytes of memory\n");
        return;
    }
    free(foo);

    buf[0] = 100;
}

Problema es que después de llamar a esta función en repetidas ocasiones el almacenamiento dinámico de Java es algo estable (que crece lentamente), pero la función de C, eventualmente puede asignar más memoria.En un nivel elevado, creo que esto es debido a que Java es la asignación de memoria para la C montón, pero no la limpieza de la ByteBuffer que señala esta memoria porque el Java ByteBuffer objeto es relativamente pequeño.

Hasta ahora he encontrado ejecución de la GC manualmente en mi función de proporcionar la necesaria limpieza, pero esto parece ser una mala idea y una mala solución.

¿Cómo puedo controlar este problema de la mejor manera que el ByteBuffer espacio está adecuadamente liberado y mi C montón de espacio es controlado?

Es mi entendimiento de que el problema incorrecta (¿hay algo que me estoy quedando mal)?

Editar:ajustar el tamaño del buffer a ser más reflexivo de mi solicitud, estoy a asignar a las imágenes aproximadamente 3000x2000...

¿Fue útil?

Solución

Creo que ha diagnosticado correctamente: que nunca se quede fuera del almacenamiento dinámico de Java, por lo que la JVM no lo hace recoger la basura, y las memorias intermedias asignadas no son liberados. El hecho de que usted no tiene problemas cuando se ejecuta manualmente GC parece confirmar esto. También podría activar el registro detallado de recogida como una confirmación secundaria.

Entonces, ¿qué se puede hacer? Bueno, lo primero que iba a tratar es mantener el tamaño inicial del montón de JVM pequeña, con el argumento de línea de comandos -Xms. Esto puede causar problemas, si el programa está en constante asignación de memoria cantidades pequeñas en el montón de Java, ya que se ejecutará GC con mayor frecuencia.

También haría uso de la pmap herramienta (o lo que es su equivalente en Windows) para examinar el mapa de memoria virtual. Es posible que usted está fragmentando la pila C, mediante la asignación de buffers de tamaño variable. Si ese es el caso, entonces usted verá una cada mapa virtual más grande, con huecos entre los bloques "anon". Y la solución no es la asignación de bloques de tamaño constante que son más grandes de lo necesario.

Otros consejos

En realidad se está enfrentando un fallo conocido en la máquina virtual de Java . La mejor solución que figuran en el informe de error es:

  • "The -XX:. MaxDirectMemorySize = opción se puede utilizar para limitar la cantidad de memoria directo utilizado Un intento de asignar memoria directa que haría que este límite se exceda causa una GC completo con el fin de provocar el procesamiento de referencia y la liberación de tampones sin referencia ".

Otras soluciones posibles incluyen:

  • Insertar ocasionales explícitas System.gc () invocaciones para asegurar que son recuperados tampones directos.
  • Reducir el tamaño de la generación joven a la fuerza GC más frecuentes.
  • explícitamente en común tampones directos a nivel de aplicación.

Si realmente quiere depender de memorias intermedias de bytes directos, entonces sugeriría poner en común a nivel de aplicación. Dependiendo de la complejidad de su aplicación, es posible que incluso simplemente almacenar en caché y reutilizar el mismo tampón (cuidado con los múltiples hilos).

Sospecho que tu problema es debido a que el uso de directa búferes de bytes.Pueden ser asignados fuera del almacenamiento dinámico de Java.

Si se llama al método con frecuencia, y la asignación de los búferes pequeños cada vez, el patrón de uso es probablemente no es un buen ajuste para un directo de búfer.

Con el fin de aislar el problema, me gustaría cambiar a un (Java) asigna el montón de búfer (sólo el uso de la allocate método en lugar de allocateDirect.Si que hace que su problema de memoria desaparece, usted ha encontrado el culpable.La siguiente pregunta sería si un directa byte del búfer tiene alguna ventaja en cuanto al rendimiento.Si no (y me imagino que no), entonces usted no necesita preocuparse acerca de cómo limpiar correctamente.

Si se ejecuta sin memoria de pila, un GC se activa automáticamente. Sin embargo, si se ejecuta fuera de la memoria directa, la GC no se activa (en la JVM de Sun por lo menos) y que acaba de llegar un OutOfMemoryError incluso si un GC liberaría suficiente memoria. He encontrado que tiene que desencadenar un GC manualmente en esta situación.

Una mejor solución puede ser la de reutilizar el mismo ByteBuffer por lo que no será necesario volver a acllocate ByteBuffers.

Para liberar de Buffer directa [ 1 ] de memoria, puede usar JNI .

La función GetDirectBufferAddress(JNIEnv* env, jobject buf) [ 3 ] de JNI 6 API puede ser utilizado para adquirir puntero a la memoria para el Buffer y luego el comando estándar free(void *ptr) en el puntero para liberar la memoria.

En lugar de escribir código como C para llamar a dicha función de Java, puede utilizar JNA 's Native.getDirectBufferPointer(Buffer) [ 6 ]

Lo único que queda después de eso es renunciar a todas las referencias al objeto Buffer. la recolección de basura de Java entonces liberar la instancia Buffer como lo hace con cualquier otro objeto que no se hace referencia.

Tenga en cuenta que Buffer directa no necesariamente en el mapa 1: 1 a una región de memoria asignada. Por ejemplo JNI API tiene NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity) [ 7 ] . Como tal, sólo deben liberar memoria de la Buffer, cuya región de asignación de memoria que sabes que es uno a uno con memoria nativa.

Asimismo, no sé si se puede liberar a un Buffer directo creado por ByteBuffer.allocateDirect(int) de Java [ 8 ] por exactamente la misma razón anterior. Podría ser JVM o Java plataforma de implementación detalles específicos si utilizan la piscina o hacer. 1: 1 asignaciones de memoria cuando la entrega de nuevos Buffers directos

A continuación sigue un fragmento ligeramente modificada de mi biblioteca en relación directa ByteBuffer [ 9 ] manipulación (utiliza Native JNA [ 10 ] y Pointer [ 11 ] clases):

/**
 * Allocate native memory and associate direct {@link ByteBuffer} with it.
 * 
 * @param bytes - How many bytes of memory to allocate for the buffer
 * @return The created {@link ByteBuffer}.
 */
public static ByteBuffer allocateByteBuffer(int bytes) {
        long lPtr = Native.malloc(bytes);
        if (lPtr == 0) throw new Error(
            "Failed to allocate direct byte buffer memory");
        return Native.getDirectByteBuffer(lPtr, bytes);
}

/**
 * Free native memory inside {@link Buffer}.
 * <p>
 * Use only buffers whose memory region you know to match one to one
 * with that of the underlying allocated memory region.
 * 
 * @param buffer - Buffer whose native memory is to be freed.
 * The class instance will remain. Don't use it anymore.
 */
public static void freeNativeBufferMemory(Buffer buffer) {
        buffer.clear();
        Pointer javaPointer = Native.getDirectBufferPointer(buffer);
        long lPtr = Pointer.nativeValue(javaPointer);
        Native.free(lPtr);
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top