samsung - memoria interna llena android sin aplicaciones
Fuga de memoria de Android ClassLoader (2)
Motivación:
Estoy usando algunas bibliotecas nativas en mi aplicación Android y quiero descargarlas de la memoria en algún momento. Las bibliotecas se descargan cuando ClassLoader que cargó la clase que cargó bibliotecas nativas obtiene basura recolectada. Inspiración: descarga nativa .
Problema:
- ClassLoader no es basura si se usa para cargar alguna clase (provoca una posible pérdida de memoria).
- Las bibliotecas nativas se pueden cargar solo en un ClassLoader en la aplicación. Si todavía hay un viejo ClassLoader colgado en algún lugar de la memoria y un nuevo ClassLoader intenta cargar las mismas bibliotecas nativas en algún momento, se lanza una excepción.
Pregunta:
- Cómo realizar la descarga de una biblioteca nativa de una manera limpia (la descarga es mi objetivo final, sin importar si es una técnica de programación deficiente o algo así).
- ¿Por qué aparece la pérdida de memoria y cómo evitarla?
En el siguiente código, simplifico el caso al omitir el código de carga de una biblioteca nativa, solo se muestra la pérdida de memoria de Classloader.
Estoy probando esto en Android KitKat 4.4.2, API 19. Dispositivo: Motorola Moto G.
Para la demostración, tengo el siguiente ClassLoader, derivado de PathClassLoader
utilizado para cargar aplicaciones de Android.
package com.demo;
import android.util.Log;
import dalvik.system.PathClassLoader;
public class LibClassLoader extends PathClassLoader {
private static final String THIS_FILE="LibClassLoader";
public LibClassLoader(String dexPath, String libraryPath, ClassLoader parent) {
super(dexPath, libraryPath, parent);
}
@Override
protected void finalize() throws Throwable {
Log.v(THIS_FILE, "Finalizing classloader " + this);
super.finalize();
}
}
Tengo EmptyClass
para cargar con LibClassLoader
.
package com.demo;
public class EmptyClass {
}
Y la pérdida de memoria se produce en el siguiente código:
final Context ctxt = this.getApplicationContext();
PackageInfo pinfo = ctxt.getPackageManager().getPackageInfo(ctxt.getPackageName(), 0);
LibClassLoader cl2 = new LibClassLoader(
pinfo.applicationInfo.publicSourceDir,
pinfo.applicationInfo.nativeLibraryDir,
ClassLoader.getSystemClassLoader()); // Important: parent cannot load EmptyClass.
if (memoryLeak){
Class<?> eCls = cl2.loadClass(EmptyClass.class.getName());
Log.v("Demo", "EmptyClass loaded: " + eCls);
eCls=null;
}
cl2=null;
// Try to invoke GC
System.runFinalization();
System.gc();
Thread.sleep(250);
System.runFinalization();
System.gc();
Thread.sleep(500);
System.runFinalization();
System.gc();
Debug.dumpHprofData("/mnt/sdcard/hprof"); // Dump heap, hardcoded path...
Lo importante a tener en cuenta es que el padre del cl2
no es ctxt.getClassLoader()
, el cargador de clases que cargó la clase de código de demostración. Esto es por diseño porque no queremos que cl2
use su padre para cargar EmptyClass
.
La memoryLeak==false
es que si memoryLeak==false
, entonces cl2
obtiene basura recolectada. Si memoryLeak==true
, aparece una pérdida de memoria. Este comportamiento no es coherente con las observaciones en JVM estándar (utilicé el cargador de clases de [ 1 ] para simular el mismo comportamiento). En JVM, en ambos casos, cl2
obtiene la basura recolectada.
También analicé el archivo de volcado de heap con Eclipse MAT, no se recolectó EmptyClass
porque la clase EmptyClass
todavía contiene referencia (ya que las clases tienen referencias en sus cargadores de clase). Esto tiene sentido. Pero EmptyClass
no fue basura recolectada sin ningún motivo, al parecer. La ruta a la raíz de GC es solo esta EmptyClass
. No he logrado persuadir a GC para finalizar EmptyClass
.
El archivo HeapDump para memoryLeak==true
se puede encontrar aquí , aquí está el proyecto Eclipse Android con una aplicación de demostración para esta pérdida de memoria.
EmptyClass
también otras variaciones de cargar EmptyClass
en el LibClassLoader
, a saber, Class.forName(...)
o cl2.findClass()
. Con / sin inicialización estática, el resultado siempre fue el mismo.
Revisé una gran cantidad de recursos en línea, no hay campos de almacenamiento en caché estáticos involucrados, hasta donde yo sé. Comprobé los códigos fuente de PathClassLoader
y sus clases principales y no encontré nada problemático.
Estaría muy agradecido por las ideas y cualquier ayuda.
Renuncia:
- Acepto que esta no es la mejor manera de hacer las cosas, si hay una mejor opción para descargar una biblioteca nativa, estaría más que feliz de usar esa opción.
- Acepto que, en general, no puedo confiar en que el GC se invoque en una ventana de tiempo. Incluso llamar a
System.gc()
es solo una pista para ejecutar GC para JVM / Dalvik. Solo me pregunto por qué hay una pérdida de memoria.
Editar 11/11/2015
Para hacerlo más claro, como escribió Erik Hellman, estoy hablando de cargar la biblioteca C / C ++ compilada por NDK, enlazada dinámicamente, con el sufijo .so.
Primero, vamos a ordenar la terminología aquí.
¿Es una biblioteca nativa con enlaces JNI que desea cargar? Es decir, ¿un archivo con el sufijo .so que se implementa en C / C ++ utilizando el Android NDK? Eso es a lo que nos referimos cuando hablamos de la biblioteca nativa. Si este es el caso, entonces la única forma de resolver esto es ejecutar la biblioteca en un proceso separado. La manera más fácil de hacerlo es crear un servicio Android donde se agrega android:process=":myNativeLibProcess"
para la entrada en el manifiesto. Este Servicio llamará a System.loadLibrary()
como de costumbre y se vinculará al Servicio desde su proceso principal utilizando Context.bindService()
.
Si se trata de un conjunto de clases de Java dentro de un archivo JAR, entonces estamos buscando algo más. Para Android, debe crear un compilador de su código de biblioteca en un archivo DEX que se coloca en un archivo JAR y se carga utilizando un DexClassLoader
, similar a lo que ha hecho en su código. Cuando desee descargar la biblioteca, debe liberar todas las referencias a las instancias que ha creado Y el cargador de clases utilizado para cargar la biblioteca. Esto le permitirá cargar una nueva versión de la biblioteca más adelante. El único problema con esto es que no recuperará toda la memoria utilizada por la biblioteca descargada en dispositivos con API nivel 19 y menor (es decir, versiones de Android que usan Dalvik VM) porque las definiciones de clase no son basura. Para Lollipop y más adelante, la nueva VM también recolectará basura de las definiciones de clase, por lo que para estos dispositivos esto funcionará mejor.
Espero que ayude.
Puede ser que puedas encontrar respuestas aquí
No estoy seguro de que sea esto lo que está buscando, pero da el método de desasignación de biblioteca real en JVM.