2012-03-09 7 views
12

Tengo una devolución de llamada JNI:JNI se acopla/desacopla la gestión de memoria hilo

void callback(Data *data, char *callbackName){ 
    JNIEnv *env; 
    jvm->AttachCurrentThread((void **)&env, NULL); 
    /* start useful code*/ 

    /* end useful code */ 
    jvm->DetachCurrentThread(); 
} 

Cuando lo ejecuto como esto (código útil vacío), consigo una pérdida de memoria. Si comento el método completo, no hay filtración. ¿Cuál es la forma correcta de unir/separar hilos?

Mi aplicación procesa datos de sonido en tiempo real, por lo que los hilos responsables del procesamiento de datos deben hacerse lo más pronto posible para estar listos para otro lote. Entonces, para estas devoluciones de llamada, creo nuevos hilos. Hay docenas o incluso cientos de ellos por segundo, se conectan a JVM, llaman a una función de devolución de llamada que vuelve a dibujar un gráfico, se desprende y muere. ¿Es esta una forma correcta de hacer esto? ¿Cómo manejar la memoria que gotea?

EDIT: errata

Aceptar He creado un código mimimal necesario:

package test; 

public class Start 
{ 
    public static void main(String[] args) throws InterruptedException{ 
     System.loadLibrary("Debug/JNITest"); 
     start(); 
    } 

    public static native void start(); 
} 

y

#include <jni.h> 
#include <Windows.h> 
#include "test_Start.h" 

JavaVM *jvm; 
DWORD WINAPI attach(__in LPVOID lpParameter); 

JNIEXPORT void JNICALL Java_test_Start_start(JNIEnv *env, jclass){ 
    env->GetJavaVM(&jvm); 
    while(true){ 
     CreateThread(NULL, 0, &(attach), NULL, 0, NULL); 
     Sleep(10); 
    } 
} 


DWORD WINAPI attach(__in LPVOID lpParameter){ 
    JNIEnv *env; 
    jvm->AttachCurrentThread((void **)&env, NULL); 
    jvm->DetachCurrentThread(); 
    return 0; 
} 

y cuando corro el perfilador VisualJM, consigo el patrón de diente de sierra de costumbre, no hay fugas allí. El uso del montón alcanzó un máximo de 5 MB. Sin embargo, observar el explorador de procesos de hecho muestra un comportamiento extraño: la memoria sube y baja lentamente, 4K por segundo durante un minuto y luego, de repente, toda la memoria asignada disminuye. Estas gotas no se corresponden con la recolección de basura (se producen con menos frecuencia y desasignan menos memoria que los dientes de sierra en el perfilador).

Así que mi mejor opción es que se trata de un comportamiento del sistema operativo que maneja decenas de miles de hilos de milisegundos de vida. ¿Algún gurú tiene una explicación para esto?

Respuesta

7

Me imaginé el problema. Estaba colgando referencias locales en el código JNI que no destruí. Cada devolución de llamada crearía una nueva referencia local, lo que provocaría una pérdida de memoria. Cuando convertí la referencia local a global, para poder reutilizarla, el problema desapareció.

14

varios puntos en llamar de nuevo en Java desde código nativo:

  • AttachCurrentThread sólo deben ser llamados si jvm-> getenv() devuelve un valor cero. Por lo general, es un no-operativo si el hilo ya está conectado, pero puede ahorrar un poco de sobrecarga.
  • DetachCurrentThread solo debe invocarse si llamó a AttachCurrentThread.
  • Evita el desprendimiento si esperas que te llame el mismo hilo en el futuro.

Dependiendo de la conducta de enhebrado de su código nativo, es posible que desee evitar de soltar y en lugar de almacenar referencias a todos los subprocesos nativos para la eliminación de terminación (si es que se tiene que hacer eso, usted puede ser capaz de confiar en la aplicación cierre para limpiar).

Si continuamente adjunta y separa subprocesos nativos, la VM debe asociar continuamente (a menudo los mismos) subprocesos con objetos Java. Algunas máquinas virtuales pueden volver a utilizar subprocesos, o asignaciones de caché temporalmente para mejorar el rendimiento, pero obtendrá un comportamiento mejor y más predecible si no confía en que la máquina virtual lo haga por usted.

+0

Veo su punto de vista y normalmente estoy de acuerdo. Los hilos que estoy creando son muy efímeros. Los creo (usando WinApi CreateThread), luego los adjunto inmediatamente a la JVM.En Java, repintan un gráfico Swing con un nuevo valor. Cuando terminen, se separarán y cesarán la ejecución (return 0), en cuyo punto deberían ser destruidos por el sistema operativo. Se usan para pasar un nuevo valor a Java. Hay aproximadamente 40 de ellos por segundo para cada canal de sonido (trabajo con hasta 32 canales). –

+0

Puede considerar la agrupación de subprocesos. Haga que sus subprocesos permanezcan por más tiempo y tome entrada de una cola, en lugar de generar continuamente nuevos subprocesos. Esto reducirá la sobrecarga de gestión de subprocesos tanto en el sistema operativo como en Java. – technomage

+0

Si está haciendo algo de Java en el JNI además de simplemente llamar a un método, es posible que también desee presionar/abrir un marco local alrededor de esas acciones. – technomage