【编者按】本文作者为来自南非约翰内斯堡的女程序员 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 Mobile Insight真实用户体验为度量标准进行 Crash 分析,监控网络请求及网络错误,提升用户留存。访问 OneAPM 官方网站感受更多应用性能优化体验,想阅读更多技术文章,请访问 OneAPM 官方技术博客

本文转自 OneAPM 官方博客

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

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

  1. Android内存优化8 内存检测工具2 LeakCanary——直白的展现Android中的内存泄露

    之前碰到的OOM问题,终于很直白的呈现在我的眼前:我尝试了MAT,但是发现不怎么会用.直到今天终于发现了这个新工具: 当我们的App中存在内存泄露时会在通知栏弹出通知: 当点击该通知时,会跳转到具体的 ...

  2. LeakCanary——直白的展现Android中的内存泄露

    之前碰到的OOM问题,终于很直白的呈现在我的眼前:我尝试了MAT,但是发现不怎么会用.直到今天终于发现了这个新工具: 当我们的App中存在内存泄露时会在通知栏弹出通知: 当点击该通知时,会跳转到具体的 ...

  3. 如何快速排查解决Android中的内存泄露问题

    概述 内存泄露是Android开发中比较常见的问题,一旦发生会导致大量内存空间得不到释放,可用内存急剧减少,导致运行卡顿,部分功能不可用甚至引发应用crash.对于复杂度比较高.多人协同开发的项目来讲 ...

  4. 在Android中解决内存溢出 – OutOfMemoryError

    原文链接:http://riggaroo.co.za/fixing-memory-leaks-in-android-outofmemoryerror/ 注:本文在原文基础上在如何判断内存是否泄露方面进 ...

  5. 56、LeakCanary——直白的展现Android中的内存泄露

    转载:http://blog.csdn.net/watermusicyes/article/details/46333925 DEMO下载地址:https://github.com/SOFTPOWER ...

  6. LeakCanary Android 和 Java 内存泄露检测

    说起内存泄漏还是挺让人头疼的,而且不是每个手机都会发生的情况,往往又不易察觉,那么今天我们就来介绍下LeakCanary这个工具 githup:https://github.com/square/le ...

  7. 系统剖析Android中的内存泄漏

    [转发]作为Android开发人员,我们或多或少都听说过内存泄漏.那么何为内存泄漏,Android中的内存泄漏又是什么样子的呢,本文将简单概括的进行一些总结. 关于内存泄露的定义,我可以理解成这样 没 ...

  8. Android DDMS检测内存泄露

    Android DDMS检测内存泄露 DDMS是Android开发包中自带工具,可以测试app性能,用于发现内存问题. 1.环境搭建 参考之前发的Android测试环境搭建相关文章,这里不再复述: 2 ...

  9. Android中的内存管理机制以及正确的使用方式

    概述 从操作系统的角度来说,内存就是一块数据存储区域,属于可被操作系统调度的资源.现代多任务(进程)的操作系统中,内存管理尤为重要,操作系统需要为每一个进程合理的分配内存资源,所以可以从两方面来理解操 ...

随机推荐

  1. Sublime Text 3编辑器的SublimeRPEL快捷键设置

    Sublime Text 3软件由于每次运行程序都要执行:Tools -> SublimeREPL -> Python -> RUN current file 菜单有点麻烦,所以现在 ...

  2. LNMT(Linux+Nginx+MySQL+Tomcat)常见性能参数调优

  3. 关于Gen生成try-catch-finally

    例1: class TestExc extends Exception{} void tryItOut () throws TestExc{} void handleExc(Object o){} v ...

  4. Gradle-5.3:依赖-管理依赖的版本(传递(transitive)\排除(exclude)\强制(force)\动态版本(+))

    什么是传递依赖 在Maven仓库中,构件通过POM(一种XML文件)来描述相关信息以及传递性依赖.Gradle 可以通过分析该文件获取获取所以依赖以及依赖的依赖和依赖的依赖的依赖,为了更加直观的表述, ...

  5. Unable to handle kernel NULL pointer dereference at virtual address 00000000问题的解决

    今天在编译好内核模块后,安装内核模块memdev.ko的时候,出现了Unable to handle kernel NULL pointer dereference at virtual addres ...

  6. php的304方式

    一般浏览器请求php是不会被缓存的,除非php直接显示的发送head 304 if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { $browserCache ...

  7. Beta阶段——Scrum 冲刺博客第三天

    一.当天站立式会议照片一张 二.每个人的工作 (有work item 的ID),并将其记录在码云项目管理中 昨天已完成的工作 实现部分question页面的制作,制作内容包括题目序号的制作,同时在最后 ...

  8. Border属性的各种变化

    本文前部分转自http://www.cnblogs.com/binyong/archive/2009/02/21/1395386.html,但是文章并未解释实现的原理,因此,后面本文也对次进行了解释. ...

  9. bootstrap table分页,重新数据查询时页码为当前页问题

    问题描述: 使用bootstrap table时遇到一个小问题,第一次查询数据未5页,翻页到第5页后,选中条件再次查询数据时,传到后端页码仍旧为5,而此时数据量小于5页,表格显示为未查询到数据. 处理 ...

  10. hadoop学习笔记(四):HDFS

    一.HDFS体系结构 1 HDFS假设条件 数据流访问 大数据集 简单相关模型 移动计算比移动数据便宜 多种软硬件平台中的可移植性 2 HDFS的设计目标 非常巨大的分布式文件系统 运行于普通硬件上 ...