gpt4 book ai didi

Android ClassLoader 内存泄漏

转载 作者:IT老高 更新时间:2023-10-28 23:16:09 25 4
gpt4 key购买 nike

动机:

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

问题:

  • 如果 ClassLoader 用于加载某些类(可能导致内存泄漏),则不会被垃圾回收。
  • 原生库只能在应用程序的一个 ClassLoader 中加载。如果内存中的某个地方仍然挂着旧的 ClassLoader,并且新的 ClassLoader 尝试在某个时间点加载相同的 native 库,则会引发异常。

问题:

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

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

我正在 Android KitKat 4.4.2,API 19 上对此进行测试。设备:摩托罗拉 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();
}
}

我有 EmptyClassLibClassLoader 一起加载。

package com.demo;
public class EmptyClass {
}

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

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

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

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

我还用 Eclipse MAT 分析了堆转储文件,cl2 没有被垃圾收集,因为类 EmptyClass 仍然持有对它的引用(因为类在其类加载器上持有引用) .这是有道理的。但是 EmptyClass 显然不是无缘无故地收集垃圾。 GC 根的路径就是这个 EmptyClass。我没能说服 GC 完成 EmptyClass

memoryLeak==true 的 HeapDump 文件位于 here , Eclipse Android 项目,带有此内存泄漏的演示应用程序here .

我还尝试了在 LibClassLoader 中加载 EmptyClass 的另一种变体,即 Class.forName(...) cl2.findClass()。有/没有静态初始化,结果总是一样的。

我查了很多网上资源,据我所知没有涉及到静态缓存字段。我检查了 PathClassLoader 的源代码,它是父类,我没有发现任何问题。

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

免责声明:

  • 我接受这不是最好的处理方式,如果有更好的选项来卸载 native 库,我会非常乐意使用该选项。
  • 我接受一般情况下我不能依赖在某个时间窗口内调用 GC。即使调用 System.gc() 也只是为 JVM/Dalvik 执行 GC 的提示。我只是想知道为什么会出现内存泄漏。

2015 年 11 月 11 日编辑

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

最佳答案

首先,让我们整理一下这里的术语。

它是您想要加载的带有 JNI 绑定(bind)的 native 库吗?也就是使用 Android NDK 在 C/C++ 中实现的后缀为 .so 的文件?这通常是我们在谈论原生库时所指的。如果是这种情况,那么解决此问题的唯一方法是在单独的进程中运行库。最简单的方法是创建一个 Android 服务,在其中为 list 中的条目添加 android:process=":myNativeLibProcess"。然后,该服务将像往常一样调用 System.loadLibrary(),然后您使用 Context.bindService() 从您的主进程绑定(bind)到该服务。

如果它是 JAR 文件中的一组 Java 类,那么我们正在寻找其他东西。对于 Android,您需要创建将库代码编译成 DEX 文件,该文件被放入 JAR 文件并使用 DexClassLoader 加载,类似于您在代码中所做的。当您想卸载库时,您需要释放对您创建的实例的所有引用以及用于加载库的类加载器。这将允许您稍后加载库的新版本。唯一的问题是,您不会在 API 级别 19 或更低级别的设备(即使用 Dalvik VM 的 Android 版本)上回收卸载库使用的所有内存,因为类定义不会被垃圾收集。对于 Lollipop 及更高版本,新 VM 还将垃圾收集类定义,因此对于这些设备,这将更好地工作。

希望对您有所帮助。

关于Android ClassLoader 内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24225572/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com