Android ClassLoader内存泄漏

ph4*_*r05 18 android garbage-collection memory-leaks classloader native-code

动机:

我在我的Android应用程序中使用了一些本机库,我想在某个时间点将它们从内存中卸载.当加载了加载本机库的类的ClassLoader被垃圾收集时,库将被卸载.灵感:原生卸货.

问题:

  • 如果ClassLoader用于加载某个类(导致可能的内存泄漏),则不会对其进行垃圾回收.
  • 本机库只能在应用程序中的一个ClassLoader中加载.如果仍有旧的ClassLoader挂在内存中,并且新的ClassLoader尝试在某个时间点加载相同的本机库,则会抛出异常.

题:

  1. 如何以干净的方式执行本机库的卸载(卸载是我的最终目标,无论它是一种糟糕的编程技术还是类似的东西).
  2. 为什么会出现内存泄漏以及如何避免内存泄漏?

在下面的代码中,我通过省略本机库加载代码来简化案例,只演示了Classloader内存泄漏.

我在Android KitKat 4.4.2,API 19上测试了它.设备:Motorola Moto G.

为了演示,我有以下ClassLoader,派生自PathClassLoader用于加载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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我有EmptyClass加载LibClassLoader.

package com.demo;
public class EmptyClass {
}
Run Code Online (Sandbox Code Playgroud)

内存泄漏是由以下代码引起的:

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...
Run Code Online (Sandbox Code Playgroud)

需要注意的重要一点是,cl2不是父ctxt.getClassLoader()类,是加载演示代码类的类加载器.这是设计原因,因为我们不想cl2使用它的父加载EmptyClass.

问题是,如果memoryLeak==false,然后cl2收集垃圾.如果memoryLeak==true出现内存泄漏.此行为与标准JVM上的观察结果不一致(我使用[ 1 ]中的类加载器来模拟相同的行为).在两种情况下,JVM cl2都会收集垃圾.

我还用Eclipse MAT分析了堆转储文件,cl2并没有进行垃圾收集,因为类EmptyClass仍然保留了对它的引用(因为类在它们的类加载器上保存引用).这是有道理的.但EmptyClass显然不是没有理由收集的垃圾.GC root的路径就是这样EmptyClass.我没有设法说服GC完成任务EmptyClass.

对于HEAPDUMP文件memoryLeak==true可以发现在这里,Eclipse的Android项目与此内存泄漏的演示应用在这里.

我还尝试了加载EmptyClassin的另一种变体LibClassLoader,即Class.forName(...)cl2.findClass().使用/不使用静态初始化时,结果始终相同.

我查了很多在线资源,据我所知,没有涉及静态缓存领域.我检查了PathClassLoader它和它的父类的源代码,我发现没有问题.

我非常感谢您的见解和任何帮助.

免责声明:

  • 我接受这不是最好的做事方式,如果有更好的选择如何卸载本机库,我会非常乐意使用该选项.
  • 我接受一般情况下我不能依赖GC在某个时间窗口中调用.甚至调用System.gc()只是为JVM/Dalvik执行GC的提示.我只是想知道为什么会有内存泄漏.

编辑11/11/2015

为了让Erik Hellman写的更清楚,我说的是加载NDK编译的C/C++库,动态链接,后缀为.so.

Ans*_*Ali 0

也许你可以在这里找到答案

我不确定这就是您正在寻找的,但它提供了 JVM 中实际的库释放方法。