查找并修复Android中的内存泄露—OutOfMemoryError

【编者按】本文作者为来自南非约翰内斯堡的女程序员 Rebecca Franks,Rebecca 热衷于安卓开发,拥有4年安卓应用开发经验。有点完美主义者,喜爱美食。

本文系国内ITOM管理平台 OneAPM 编译呈现,以下为正文。

Android 程序中很容易出现内存泄露问题。毫无戒心的开发者可能每天都会造成一些内存泄露,却不自知。你可能从未注意过这类错误,或者甚至都不知道它们的存在。直到你遇到下面这样的异常:

java.lang.OutOfMemoryError: Failed to allocate a 4308492 byte allocation with 467872 free bytes and 456KB until OOM
at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:609)
at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:444)
at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:988)
at android.content.res.Resources.loadDrawableForCookie(Resources.java:2580)
at android.content.res.Resources.loadDrawable(Resources.java:2487)
at android.content.res.Resources.getDrawable(Resources.java:814)
at android.content.res.Resources.getDrawable(Resources.java:767)
at com.nostra13.universalimageloader.core.DisplayImageOptions.getImageOnLoading(DisplayImageOptions.java:134)

啥?这是什么意思?是说我的位图(bitmap)太大了吗?

不幸的是,这种堆栈跟踪往往带点迷惑性。通常,如果遇到 OutOfMemoryError 错误,十有八九是因为内存泄露。当笔者第一次遇到这种堆栈跟踪时,也感到迷惑不解,想着是不是位图太大了……实际上,我那会儿真是大错特错。

什么是内存泄露?

内存泄露是指程序释放废弃内存失败,导致性能受损或出现中断。

Android 程序中的内存泄露是如何产生的?

Android 程序中的内存泄露很容易产生,这也是问题的一部分。然而,最大的问题在于 Android Context(上下文) 对象。

每个 app 都有一个全局的应用上下文对象( getApplicationContext())。每个 Activity (活动)都是 Context 的子类,存储着与当前活动相关的信息。通常,内存泄露都与已泄露的活动(leaked activtiy)相关。

通常,一般的开发者会把上下文对象(context object)传给需要的线程。创建一些静态的 TextViews 以存储指向活动的引用。但是,你懂的,这样可行吗?

在此情况下,如果使用内存监视器就会发现,app 的内存使用率不断增加,正如下面的 Android 内存监控器所示:


存在内存泄露问题的 app 在运行时,Android 内存监控器的情况


解决内存泄露问题后,Android 内存监控器的情况

如你所见,在第一张图中,app 永远都无法回收一部分已经使用的内存。在 OutOfMemoryError 错误出现之前,它一度使用了300MB的内存。而第二张图则显示,app 能顺利进行垃圾回收,重得一部分内存,从而保持相当稳定的内存使用量。

如何避免内存泄露?

  • 避免在 activity 或 fragment 之外传递 Context 对象。
  • 永远永远不要创建静态的 Context 或 View 对象,或者将二者存储于静态变量中。这是内存泄露的首要标志。

    private static TextView textView; //DO NOT DO THIS
    private static Context context; //DO NOT DO THIS

  • 总是记得在 onPause() 或 onDestroy() 方法中的取消注册监听器(listeners)。这包括 Android
    监听器,以及位置服务、显示管理器服务,还有自定义的一些监听器。
  • 不要在 AsyncTasks(异步任务)或后台线程中存储指向 activities 的强引用。Activity 可能会关闭,但是
    AsyncTask 会继续执行,一直保存着对该 activity 的引用。
  • 如果可以,使用 Context-application (getApplicationContext()),而不是某个 activity
    的 Context 对象。
  • 尽力避免使用非静态的内部类。将引用存储至某个 Activity 或 View 内部会导致内存泄露。如果不得不存储引用,请使用
    WeakReference

如何修复内存泄露问题?

修复内存泄露问题需要许多实践,不断尝试、试错,才能取得成功。通常,内存泄露并不容易定位。值得庆幸的是,有许多现成的工具可以帮你找出潜在的泄露问题。

1、打开 Android Studio,打开 Android Monitor(监控器)选项。
2、运行你的应用,从可选应用中进行选择你的应用,并运行之。
3、在 app 中进行一些操作,以达到类似的效果。譬如笔者,打开了一个新视频,播放了50次。(为此,笔者写了一个测试程序。)
4、此处的关键,是在出现 OutOfMemoryException 异常之前捕获应用的问题。
5、点击 Android 监控器中的内存选项。

6、你会看到一张动态绘制的图表。准备好之后,点击“启动垃圾回收(Initiate GC)“(红色的垃圾卡车图标)。
7、点击“倾倒 Java 堆内存(Dump Java Heap)”,之后等待数秒。(卡车图标下面带有绿色箭头的图标)。这会生成一个 .hprof 文件,你可以用来分析内存使用率。
8、不幸的是,Android Studio Hprof 文件查看器不具备 Eclipse 内存分析器的所有小工具。因此,你需要安装 MAT。
9、运行下面的指令,将 Android 的 .hprof 文件转换为 MAT 能够理解的格式。(hprof-conv 工具位于 sdk 的平台工具文件夹下)

./hprof-conv path/file.hprof exitPath/heap-converted.hprof

10、转换完成后,在 MAT 中打开该文件。选择“泄露疑点报告(Leak Suspects Report)”,之后点击完成。

打开 Eclipse 内存分析器 —— 选择泄露疑点报告
11、点击顶部的三个蓝色柱形图标,“为任意对象集合创建一个直方图”。你会看到占用内存的一列对象。

Eclipse 内存分析器 — 直方图
12、查看这么多对象或许会让人摸不到头脑。其实,你可以根据类名进行过滤,因此笔者建议你在类名过滤器中输入类名。
查找并修复Android中的内存泄露—OutOfMemoryError 技术分享 第6张根据类名在 Eclipse 内存分析器中过滤对象

13、现在,我们看到 VideoDetailActivity 存在9个实例。这显然是不对的,因为我们其实只需要一个。进一步查看谁保存着 VideoDetailActivity 的引用,右键点击该项目,选择“合并垃圾回收根的最短路径(Merge Paths to Shortest GC Root)”,然后点击“排除所有虚/弱/软引用(exclude all phantom/weak/soft etc. references)。”

Eclipse 内存分析器——合并垃圾回收根的最短路径

现在,保存着引用的线程就会显示出来。之后,你可以追根溯源,找到存储该 activity 引用的具体实例。

14、根据下面的信息,显然,有一个 DisplayListener 对象在登记之后从未注销过。

Eclipse 内存分析器 — 内存泄露识别

因此,对这个此前登记的显示监听器(display listener)调用注销方法,就能解决此内存泄露问题。

DisplayManager displayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);displayManager.unregisterDisplayListener(listener);

不过,并非所有的内存泄露都这么容易找到,也有一些非常难找。但是,希望本文能使你开始寻找问题根源,并避免潜在的内存泄露问题。此外,还有许多有助于寻找内存泄露问题的工具,点击此处进行查看。

参考链接:

本文转自 OneAPM 官方博客

原文地址:http://riggaroo.co.za/fixing-memory-leaks-in-android-outofmemoryerror/

时间: 2025-01-20 20:05:48

查找并修复Android中的内存泄露—OutOfMemoryError的相关文章

Android编程中避免内存泄露的方法总结_Android

Android的应用被限制为最多占用16m的内存,至少在T-Mobile G1上是这样的(当然现在已经有几百兆的内存可以用了--译者注).它包括电话本身占用的和开发者可以使用的两部分.即使你没有占用全部内存的打算,你也应该尽量少的使用内存,以免别的应用在运行的时候关闭你的应用.Android能在内存中保持的应用越多,用户在切换应用的时候就越快.作为我的一项工作,我仔细研究了Android应用的内存泄露问题,大多数情况下它们是由同一个错误引起的,那就是对一个上下文(Context)保持了长时间的引

Android 和 Java 内存泄露检测工具——LeakCanary

LeakCanary Android 和 Java 内存泄露检测. "A small leak will sink a great ship." - Benjamin Franklin 千里之堤, 毁于蚁穴. -- <韩非子·喻老> demo 一个非常简单的 LeakCanary demo: https://github.com/liaohuqiu/leakcanary-demo 开始使用 在 build.gradle 中加入引用,不同的编译使用不同的引用: depende

Android开发——避免内存泄露

Android开发--避免内存泄露 本文翻译自Avoiding memory leak--Post by Romain Guy 著作权归原作者所有.转载请注明出处,由JohnTsai翻译 Android应用被分配的堆的大小限制为16MB.这对于手机来说已经很多了,但对于一些开发者想获得的来说仍旧不够.即使你没有计划使用所有的这些内存.你应该尽可能的少用以避免其他应用在运行时因为内存不足而被杀掉.Android内存中保存的应用越多,用户在应用间切换得越快.作为我工作的一部分,我在Android应用

浅谈Java编程中的内存泄露情况_java

必须先要了解的 1.c/c++是程序员自己管理内存,Java内存是由GC自动回收的. 我虽然不是很熟悉C++,不过这个应该没有犯常识性错误吧. 2.什么是内存泄露? 内存泄露是指系统中存在无法回收的内存,有时候会造成内存不足或系统崩溃. 在C/C++中分配了内存不释放的情况就是内存泄露. 3.Java存在内存泄露 我们必须先承认这个,才可以接着讨论.虽然Java存在内存泄露,但是基本上不用很关心它,特别是那些对代码本身就不讲究的就更不要去关心这个了. Java中的内存泄露当然是指:存在无用但是垃

使用Android Studio检测内存泄露(LeakCanary)_Android

内存泄露,是Android开发者最头疼的事.可能一处小小的内存泄露,都可能是毁千里之堤的蚁穴. 怎么才能检测内存泄露呢? AndroidStudio 中Memory控件台(显示器)提供了一个内存监视器.我们可以通过它方便地查看应用程序的性能和内存使用情况,从而也就可以找到需要释放对象,查找内存泄漏等. 熟悉Memory界面 打开日志控制台,有一个标签Memory ,我们可以在这个界面分析当前程序使用的内存情况. 运行要监控的程序(APP)后,打开Android Monitor控制台窗口,可以看到

使用新版Android Studio检测内存泄露和性能

内存泄露,是Android开发者最头疼的事.可能一处小小的内存泄露,都可能是毁于千里之堤的蚁穴. 怎么才能检测内存泄露呢?网上教程非常多,不过很多都是使用Eclipse检测的, 其实1.3版本以后的Android Studio 检测内存非常方便, 如果结合上MAT工具,LeakCanary插件,一切就变得so easy了. 熟悉Android Studio界面 工欲善其事,必先利其器.我们接下来先来熟悉下Android Studio的界面 一般分析内存泄露, 首先运行程序,打开日志控制台,有一个

android的GC内存泄露问题_Android

1. android内存泄露概念 不少人认为JAVA程序,因为有垃圾回收机制,应该没有内存泄露.其实如果我们一个程序中,已经不再使用某个对象,但是因为仍然有引用指向它,垃圾回收器就无法回收它,当然该对象占用的内存就无法被使用,这就造成了内存泄露.如果我们的java运行很久,而这种内存泄露不断的发生,最后就没内存可用了.当然java的,内存泄漏和C/C++是不一样的.如果java程序完全结束后,它所有的对象就都不可达了,系统就可以对他们进行垃圾回收,它的内存泄露仅仅限于它本身,而不会影响整个系统的

实例详解Java中ThreadLocal内存泄露_java

案例与分析 问题背景 在 Tomcat 中,下面的代码都在 webapp 内,会导致WebappClassLoader泄漏,无法被回收. public class MyCounter { private int count = 0; public void increment() { count++; } public int getCount() { return count; } } public class MyThreadLocal extends ThreadLocal<MyCount

Android App调试内存泄露之Cursor篇_Android

最近在工作中处理了一些内存泄露的问题,在这个过程中我尤其发现了一些基本的问题反而忽略导致内存泄露,比如静态变量,cursor关闭,线程,定时器,反注册,bitmap等等,我稍微统计并总结了一下,当然了,这些问题这么说起来比较笼统,接下来我会根据问题,把一些实例代码贴出来,一步一步分析,在具体的场景下,用行之有效的方法,找出泄露的根本原因,并给出解决方案. 现在,就从cursor关闭的问题开始把,谁都知道cursor要关闭,但是往往相反,人们却常常忘记关闭,因为真正的应用场景可能并非理想化的简单.