【编者按】本文作者为来自南非约翰内斯堡的女程序员 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. java调用存储过程、存储函数

    需要用到的接口 接口 CallableStatement JDK文档对改接口的说明: public interface CallableStatement extends PreparedStatem ...

  2. vue制作小程序--server

    服务端代码,开发者工具有提供 指南 https//cloud.tencent.com/document/product/619/11442 参考文档API https://developers.wei ...

  3. Android Studio修改字体大小

    android studio提供的主题是不能修改字体和字体大小的,如果要修改大小就的另存一份自定义的主题了. 1.启动Android Studio,菜单"File"-"S ...

  4. docker “no space left on device”问题定位解决

    在paas环境上使用docker加载镜像的时候出现了如下问题 第一反应应该是存储镜像的路径磁盘满了 docker info查看docker的根路径,可以看到为/opt/docker: 查看/opt/d ...

  5. (转) Java我的高效编程之环境搭建

    前言:刚毕业, 工作之余写博客有利于提高技术,更是能帮助人,接下来会认认真真写好每一篇博客.希望大家多多支持.废话不多说,马上开始.这是一篇环境搭建的博客. jdk+eclipse+svn+maven ...

  6. 开始使用 Vuejs 2.0 --- 组件间数据传递

    Vue1.0组件间传递 使用$on()监听事件: 使用$emit()在它上面触发事件: 使用$dispatch()派发事件,事件沿着父链冒泡: 使用$broadcast()广播事件,事件向下传导给所有 ...

  7. 15.Generator 函数的语法

    Generator 函数的语法 Generator 函数的语法 简介 基本概念 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同.本章详细介绍 Generat ...

  8. VB如何连接访问数据库Access

    VB如何连接访问数据库Access 听语音 | 浏览:10675 | 更新:2015-05-05 11:26 | 标签:连接 access 1 2 3 4 5 6 7 分步阅读 VB即Visual B ...

  9. GetHashCode方法学习

    GetHashCode方法我的理解是做两个对象的比较,每个对象,不管是值类型还是应用类型都提供这个基本函数,都可以去重写它.GetHashTable通常用于HashTable.List<> ...

  10. Node.js Cookie管理

    Cookie 管理 我们可以使用中间件向 Node.js 服务器发送 cookie 信息,以下代码输出了客户端发送的 cookie 信息: var express=require('express') ...