当前位置: 首页 > 知识库问答 >
问题:

Android ClassLoader内存泄漏

颜永怡
2023-03-14

我正在Android应用程序中使用一些本机库,我想在某个时间点从内存中卸载它们。当装入本机库的类的类装入器被垃圾回收时,库被卸载。灵感:原生卸载。

  • 如果ClassLoader用于加载某些类(可能导致内存泄漏),则不会收集垃圾。
  • 本机库只能在应用程序中的一个ClassLoader中加载。如果仍然有旧的ClassLoader挂在内存中的某个地方,并且一个新的ClassLoader试图在某个时间点加载相同的本机库,则会引发异常。
  1. 如何以干净的方式执行本机库的卸载(卸载是我的最终目标,无论它是一种糟糕的编程技术还是类似的技术)
  2. 为什么会出现内存泄漏以及如何避免

在下面的代码中,我通过省略本机库加载代码简化了这个例子,只演示了类加载器内存泄漏。

我在Android KitKat 4.4.2, API 19上测试这个。设备:摩托罗拉Moto G.

在演示中,我使用了下面的类加载器,它来自用于加载Android应用程序的PathClassLoader

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();
    }
}

我用LibClassLoader加载EmptyClass

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不是无缘无故收集的垃圾。到GC根的路径就是这个UrtyClass。我还没有设法说服GC最终确定AsutyClass

可以在这里找到memoryLeak==true的HeapDump文件,Eclipse Android项目在这里有一个内存泄漏的演示应用程序。

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

我查看了很多在线资源,据我所知,没有涉及静态缓存字段。我检查了PathClassLoader及其父类的源代码,没有发现任何问题。

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

  • 我承认这不是最好的方法,如果有更好的方法来卸载本机库,我会非常乐意使用这个方法

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


共有2个答案

宗政颖逸
2023-03-14

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

我不确定您正在寻找的是什么,但它给出了JVM中实际的库释放方法。

慕阳文
2023-03-14

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

您想加载的是带有JNI绑定的本机库吗?即,带有后缀的文件。那么这是在C/C中使用Android NDK实现的?这通常是我们在谈论本机库时所指的。如果是这样,那么解决这个问题的唯一方法就是在单独的进程中运行库。最简单的方法是创建一个Android服务,在其中为清单中的条目添加Android:process=“:myNativeLibProcess”。然后,该服务将调用系统。loadLibrary()与往常一样,您可以使用上下文从主进程绑定到服务。bindService()

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

希望有帮助。

 类似资料:
  • 问题内容: 我认为我的android应用正在泄漏内存。我不是绝对确定这是问题所在。 应用程序打开时经常崩溃,并且logcat尝试加载位图图像时会显示“内存不足”异常。 崩溃后,我重新打开了该应用程序,它运行正常。Logcat会显示许多“ gc”,并且JIT表会不时地向上调整大小,而不会向下调整,直到应用程序因内存不足错误而崩溃。 这听起来像是内存泄漏吗?如果是这样,我该如何定位和关闭泄漏点。 这是

  • 问题内容: 我一直在追寻内存泄漏(由“ valgrind –leak-check = yes”报告),它似乎来自ALSA。这段代码已经存在于自由世界中一段时间​​了,所以我猜这是我做错的事情。 输出看起来像这样: 并继续一些页面 这是由于我在一个项目中使用ALSA并开始看到这种巨大的泄漏……或者至少是所说泄漏的报告。 所以问题是:是我,ALSA或valgrind在这里遇到问题吗? 问题答案: ht

  • 问题内容: 我有一个长时间运行的脚本,如果让脚本运行足够长的时间,它将消耗系统上的所有内存。 在不详细介绍脚本的情况下,我有两个问题: 是否有可遵循的“最佳实践”,以防止泄漏发生? 有什么技术可以调试Python中的内存泄漏? 问题答案: 看看这篇文章:跟踪python内存泄漏 另外,请注意,垃圾收集模块实际上可以设置调试标志。看一下功能。此外,请查看Gnibbler的这段代码,以确定调用后已创建

  • 本文向大家介绍Java 内存泄漏,包括了Java 内存泄漏的使用技巧和注意事项,需要的朋友参考一下 在Java中,垃圾回收(析构函数的工作)是使用垃圾回收自动完成的。但是,如果代码中有引用它们的对象怎么办?它无法取消分配,即无法清除其内存。如果这种情况一再发生,并且创建或引用的对象根本没有被使用,它们就会变得无用。这就是所谓的内存泄漏。 如果超过了内存限制,则程序将通过抛出错误(即“ OutOfM

  • 问题内容: 我使用Informix遇到了一个奇怪的问题(具体来说,我使用的是IBM.Data.Informix命名空间,即4.10 Client SDK)。我正在使用ODBC连接到IBM Informix数据库,并且遇到内存泄漏问题。该文档相当稀疏,并且我只能使用当前安装的驱动程序/ SDK。这是我用于数据库上下文的代码: } 我已尝试处置并关闭所有可以的连接,但这似乎无济于事。我是否缺少某些东西

  • 我们有一个基于go-socket.io(socket.ioGo语言实现)和大猩猩网络插座的网络插座服务,但是似乎有内存泄漏问题。即使我使用调试,HeapAlloc也总是在增加。FreeOSMemroy强制释放内存。 服务很简单。它将使用jwt令牌对传入请求进行身份验证,如果身份验证成功,则将创建一个go套接字。io conn基于gorilla websocket conn。但现在似乎是net/te

  • 问题内容: 我发现使用是众所周知的与相关的内存问题。 使用中是否存在内存泄漏? 如果是,解决方法是什么? 以下链接显示了Java中子字符串的正确用法。 http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4513622 另外一个博客谈论子字符串中可能的MLK。 http://nflath.com/2009/07/the-dangers-of- st

  • 我有一个这样的静态ExpressJS服务器: 当我启动服务器时,它使用20MB的v8堆。如果我每秒重新加载一个页面,则使用的堆会不断增长。4小时后,使用的v8堆将达到40MB。v8堆的总容量达到80MB,RSS(进程使用的总内存)达到130MB。 为什么这个简单而静态的服务器使用这么多内存?这似乎是内存泄漏。如果我不停止页面重新加载,使用的内存会继续增长。 如果像这样一个简单的静态服务器使用了太多