Android Harmony的JarURLConnectionImpl中的内存泄漏?

我正在开发一个Android应用程序,我们正在调查内存使用情况。

从hprof看堆转储,我们看到在JarURLConnectionImpl的静态caching中使用了将近2M(22%的堆):

在这里输入图像说明

查看JarURLConnectionImpl的源代码 ,看起来条目被添加到静态jarCachevariables,但从未被删除。

如果他们从来没有被删除是真的,那么这就是潜在的内存泄漏

这是泄漏吗? 有没有修复或解决方法?

这是一个丑陋的解决方法:

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

然后,定期运行以下内容:

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

我在创build活动时运行它,所以每次创build一个活动时都会执行它。 丑陋的HashMap条目似乎并没有经常被重新创build,但它似乎偶尔会重新出现,所以仅仅运行一次代码是不够的。

这绝对是一个讨厌的内存泄漏。 我已经开了一个问题 ,因为没有人似乎已经报告了。

感谢“丑陋的解决方法”emmby,这是有帮助的。 尽pipe潜在的性能影响,更安全的方法是完全禁用URLConnectioncaching。 由于URLConnection.defaultUseCaches标志是静态的,并且正如您所猜测的,它是每个实例的useCaches标志的默认值,所以您可以将其设置为false,并且不会有更多的实例将caching它们的连接。 这将影响URLConnection的所有实现,所以它可能会有比预期更远的效果,但我认为这是一个合理的折衷。

你可以像这样创build一个简单的类,并在应用程序的onCreate()中尽早实例化:

 public class URLConnectionNoCache extends URLConnection { protected URLConnectionNoCache(URL url) { super(url); setDefaultUseCaches(false); } public void connect() throws IOException { } } 

有趣的是,因为这是在您的应用程序加载并运行之后发生的,所以系统库应该已经被caching,并且这只会阻止进一步的caching,所以这可能会给出最好的折衷:不cachingapk,而允许caching系统jar子的性能好处。

在做这个之前,我修改了emmby的解决scheme,使它成为一个独立的类,创build一个后台线程来定期清除caching。 我限制它只是清除应用程序的apk,虽然这可以放松,如果需要的话。 这里要担心的是你在修改对象时可能正在使用,这通常不是一件好事。 如果你想要走这条路线,只需要用上下文来调用start()方法,例如在你的应用程序的onCreate()中。

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