Fuga de memoria de Android en JarURLConnectionImpl de Apache Harmony?
Estoy trabajando en una aplicación para Android y estamos investigando el uso de la memoria.
Mirando un volcado de montón de hprof, estamos viendo casi 2M (22% de nuestro montón) que se utiliza en una caché estática en JarURLConnectionImpl:
- AdMob: "actividad ha filtrado ServiceConnection" y "AdvertisingIdClient unbindService ha fallado."
- ¿Puede alguien explicarme por qué esta filtración?
- Cómo corregir un "SQLiteConnection para la base de datos gms" que se filtró
- Fuga de memoria debido a android.widget.BubblePopupHelper
- Sqlite base de datos FUGA ENCONTRADA excepción en android?
Observando el código fuente de JarURLConnectionImpl , parece que las entradas se agregan a la variable jarCache estática, pero nunca se eliminan.
Si es cierto que nunca se eliminan, eso me parece una posible pérdida de memoria.
¿Es esto una fuga? ¿Hay una solución o solución?
- Fuga de memoria de Android entre las actividades
- ¿Cómo puedo saber cuánta memoria heap en un momento dado?
- Fuga de AdActivity en AdMob (SDK 7.0) para Android
- Error de memoria de mapa de bits - Android
- Cómo depurar la pérdida de memoria donde las instancias de excepción en el volcado heap no tienen referencias entrantes?
- Descripción de las fugas de memoria en una aplicación de Android
- Ejemplo de pérdida de memoria de Android de Google I / O
- Cómo liberar memoria en android para evitar fugas de memoria
He aquí una solución fea:
private static HashMap<URL,JarFile> jarCache; static { try { Class<?> jarURLConnectionImplClass = Class.forName("org.apache.harmony.luni.internal.net.www.protocol.jar.JarURLConnectionImpl"); final Field jarCacheField = jarURLConnectionImplClass.getDeclaredField("jarCache"); jarCacheField.setAccessible(true); //noinspection unchecked jarCache = (HashMap<URL, JarFile>) jarCacheField.get(null); } catch(Exception e) { // ignored } }
A continuación, ejecute periódicamente lo siguiente:
// HACK http://stackoverflow.com/questions/14610350/android-memory-leak-in-apache-harmonys-jarurlconnectionimpl if( jarCache!=null ) { try { for ( final Iterator<Map.Entry<URL, JarFile>> iterator = jarCache.entrySet().iterator(); iterator.hasNext(); ) { final Map.Entry<URL, JarFile> e = iterator.next(); final URL url = e.getKey(); if (Strings.toString(url).endsWith(".apk")) { Log.i(TAG,"Removing static hashmap entry for " + url); try { final JarFile jarFile = e.getValue(); jarFile.close(); iterator.remove(); } catch( Exception f ) { Log.e(TAG,"Error removing hashmap entry for "+ url,f); } } } } catch( Exception e ) { // ignored } }
Lo ejecuto en la creación de actividad para que se ejecute cada vez que se crea una de mis actividades. La fea entrada hashmap no parece ser recreada todo lo que a menudo, pero parece reaparecer de vez en cuando, por lo que no es suficiente para ejecutar este código una vez.
Esta es definitivamente una pérdida de memoria desagradable. He abierto un problema para él ya que nadie más parece haberlo informado.
Gracias por la "horrible solución" emmby, que fue útil. Un enfoque más seguro, aunque potencialmente con un impacto en el rendimiento, es desactivar el almacenamiento en caché URLConnection por completo. Dado que el indicador URLConnection.defaultUseCaches es estático y, como se puede adivinar, es el valor por defecto para el indicador useCaches de cada instancia, sólo puede configurarlo como false y no más instancias almacenarán sus conexiones en caché. Esto afectará a todas las implementaciones de URLConnection, por lo que puede tener efectos de mayor alcance de lo deseado, pero creo que es un trade-off razonable.
Simplemente puede crear una clase simple como esta e instanciarla muy pronto en la aplicación onCreate ():
public class URLConnectionNoCache extends URLConnection { protected URLConnectionNoCache(URL url) { super(url); setDefaultUseCaches(false); } public void connect() throws IOException { } }
Lo interesante es que, puesto que esto ocurre después de cargar y ejecutar tu aplicación, las libs del sistema ya deberían estar en caché, y esto sólo evitará el almacenamiento en caché, por lo que probablemente da el mejor equilibrio posible: no poner en caché tu apk mientras Ventajas del funcionamiento de almacenar en caché las jarras del sistema.
Antes de hacer esto, modifiqué la solución de emmby un poco para convertirla en una clase autónoma que crea un subproceso de fondo para limpiar periódicamente el caché. Y lo restringí para borrar el apk de la aplicación, aunque eso puede ser relajado si lo desea. La cosa a preocuparse aquí es que usted está modificando los objetos mientras pueden estar en uso, que generalmente no es una buena cosa. Si desea seguir esta ruta, sólo tiene que llamar al método start () con un contexto, por ejemplo, en la aplicación onCreate () de su aplicación.
package com.example; import java.lang.reflect.Field; import java.net.URL; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.jar.JarFile; import java.util.regex.Pattern; import android.content.Context; // hack to remove memory leak in JarURLConnectionImpl // from http://stackoverflow.com/questions/14610350/android-memory-leak-in-apache-harmonys-jarurlconnectionimpl public class JarURLMonitor { private static JarURLMonitor instance; private Pattern pat; private Field jarCacheField; public volatile boolean stop; private static final long CHECK_INTERVAL = 60 * 1000; public static synchronized void start(Context context) { if (instance == null) { instance = new JarURLMonitor(context); } } public static synchronized void stop() { if (instance != null) { instance.stop = true; } } private JarURLMonitor(Context context) { // get jar cache field try { final Class<?> cls = Class.forName("libcore.net.url.JarURLConnectionImpl"); jarCacheField = cls.getDeclaredField("jarCache"); jarCacheField.setAccessible(true); } catch (Exception e) { // log } if (jarCacheField != null) { // create pattern that matches our package: eg /data/app/<pkgname>-1.apk pat = Pattern.compile("^.*/" + context.getPackageName() + "-.*\\.apk$"); // start background thread to check it new Thread("JarURLMonitor") { @Override public void run() { try { while (!stop) { checkJarCache(); Thread.sleep(CHECK_INTERVAL); } } catch (Exception e) { // log } } }.start(); } } private void checkJarCache() throws Exception { @SuppressWarnings("unchecked") final HashMap<URL, JarFile> jarCache = (HashMap<URL, JarFile>)jarCacheField.get(null); final Iterator<Map.Entry<URL, JarFile>> iterator = jarCache.entrySet().iterator(); while (iterator.hasNext()) { final Map.Entry<URL, JarFile> entry = iterator.next(); final JarFile jarFile = entry.getValue(); final String file = jarFile.getName(); if (pat.matcher(file).matches()) { try { jarFile.close(); iterator.remove(); } catch (Exception e) { // log } } } } }
- ¿Cuál es la diferencia entre EasyTracker y el Tracker regular?
- VectorDrawable no se representa correctamente en API 23