热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

一次解决AndroidOOM的经历

OOM**OOM(OutOfMemory)**是Android应用开发中相信每个人都遇到过的问题,而OOM在crashlog中的stacktrace一般没有实际意义

OOM

**OOM(Out Of Memory)**是Android应用开发中相信每个人都遇到过的问题,而OOM在crash log中的stack trace一般没有实际意义,因为是在分配内存的时候才会抛出OOM异常,而这个时候的stack trace和OOM的原因没有任何关系。所以OOM问题的定位和分析就需要多花费一些功夫。

下面,我就结合一个例子,来讲讲怎么定位OOM问题。

问题
在程序员们把代码写完,基本流程测试无误,准备要发布的时候,云测的结果却是:一大波OOM异常。没办法,只好重新打开电脑定位问题。
由于不是我一个人写的代码,所以直接看代码定位问题有点困难,这个时候就要上工具了。


1. 定性问题

1.1 定位问题,先定性
这是一个内存泄漏导致的OOM,还是应用本身设计不当,导致一次需要加载的内存过多,导致的OOM?
定位问题的方法很简单,看内存是不是一直在增长,如果在使用的过程中内存一直在增长,则很有可能是内存泄漏导致的。
1.2 推荐使用的工具
Android Studio自带的Memory Monitor,很简单的一个小工具,能够把应用内存实时展现出来,简单到我认为不需要多说了。如下图:
在这里插入图片描述在摆弄了几分钟之后,我发现了几个问题:


  1. app刚刚启动的时候,内存占用就很大,因为用了很多的贴图(设计不当)
  2. app在进入播放界面并且推出之后,即使什么操作都不做,内存一直在缓慢增长(内存泄漏)
  3. app在我退出再进的时候,内存占用几乎翻番(内存泄漏)
    其中,问题2很快就能猜出来,播放结束后MediaPlayer没有被释放,之后验证了下,解决。

2. 设计不当?优化!

这个问题就很泛了,比如,纯色的背景就不用图片来实现,不用超过需要像素值的图片,不加载显示范围之外的图片,等等。
最终找到了几张图片,只需要1280x720,给的图是1920x1080。同时简单实现了图片的LazyLoading。问题1基本凑合搞定。


3. 内存泄漏?

如果在使用过程中,内存曲线一直是上涨趋势,这就很有可能存在内存泄漏了。

3.1 查看堆的信息
Android SDK的工具集中就提供这样一个工具:Device Monitor。使用方法如下

1. 打开Android Device Monitor
2. 选择你要调试的进程
3. 点击Update Heap按钮

基本情况如下图:
在这里插入图片描述
可以看到基本的堆情况,以及堆内对象的概览。

3.2 查看Activity泄漏

常见的内存泄漏很多都是由于Activity对象不能被释放导致的,用下面的adb命令可以快速的定位到这个问题:
在这里插入图片描述
得到结果如下:
在这里插入图片描述
其中的ViewRootImpl、Activities、AppContexts数量很值得关注。
关于其他的输出含义,见developer docs
3.3 取heap dump
在基本确定内存有泄漏之后,就需要定位具体是哪个对象泄漏,好定位相关代码,这个时候就可以对heap dump进行分析。
heap dump就是一个内存heap的快照,可以用来很具体的分析内存里到底有什么。
首先,我们用Device Manager需要导出一个heap dump,如图所示
在这里插入图片描述
然后转化成java dump,用 Eclipse Memory Analyzer Tool (MAT) 来分析。
用platform-tools里的hprof-conv来转化:
在这里插入图片描述
转化完成之后,我们就可以用MAT来打开分析。
4. Eclipse Memory Analyzer Tool (MAT)
当我们用MAT打开dump时,看到的基本界面如下:
在这里插入图片描述
这里有几个概念需要了解:
1. Shallow Heap & Retained Heap
Shallow Heap的大小就是对象所占的内存空间,一般一个Object持有一个引用会需要32或者64bit的空间(取决于JVM)。
由于A对象持有B对象引用会导致B对象在GC中不会被销毁,所以由于被对象直接或者间接持有引用而不会被释放的对象的占用的内存总和,就是Retained Heap。
简单来说,Shallow heap就是对象占用的空间,Retained Heap就是假如对象被释放,连带能够释放出来的空间。

2. Dominator Tree
定义在这里。简单来说,Dominator Tree可以很好地观察Retained Heap大小。
通常,在dump中查看Histogram和dominator_tree就可以看出一些端倪,例如这个例子中,histogram图中,占用内存最多的是byte[]对象,通过右键 -> List object菜单,可以看到
在这里插入图片描述
基本上都是Bitmap对象,而且Bitmap有重复数据:
在这里插入图片描述
到这一步,可以继续追查是谁导致两份相同的数据不能得到释放,通过
右键 -> Path to CG root功能,可以追查到最终是被哪个对象持有导致不能被释放,结果如下:
在这里插入图片描述
到这里,问题基本就明白了:

DBHelper是一个单例静态对象,这个对象会持有一个Context对象,在代码中被当成Context传入DBHelper的是一个Activity对象,所以这个Activity不会被释放;当这个Activity被销毁重建时,新的Activity会重新加载ContentView,而老的Activity所持有的整个View的树全部不会被释放,同时View持有的图片也不会被释放,导致内存不够。
至此,整个问题基本已经找到,同时告诉我们,用单例最好不要持有Context对象,如果需求需要,查看下这个设计是否合理,以及使用的时候谨慎。


一个常用的MAT技巧

在分析内存占用的时候,通常可以看到各种占用最多的时Bitmap对象,这个时候,如果能直接显示这个Bitmap内容,那么找起来会方便很多。下面是一个查看的方法:
stackoverflow上回答见这里

结尾
整个过程我基本是参照Google官方文档 Investigating Your RAM Usage,可以读下这份文档。


推荐阅读
  • MySQL Decimal 类型的最大值解析及其在数据处理中的应用艺术
    在关系型数据库中,表的设计与SQL语句的编写对性能的影响至关重要,甚至可占到90%以上。本文将重点探讨MySQL中Decimal类型的最大值及其在数据处理中的应用技巧,通过实例分析和优化建议,帮助读者深入理解并掌握这一重要知识点。 ... [详细]
  • 本文深入探讨了MDK链接脚本的应用与优化技巧。首先,文章介绍了链接脚本的基本概念及其在嵌入式系统开发中的重要性。接着,通过具体实例详细分析了链接脚本的结构和功能,特别是在程序在FLASH中运行时,如何优化链接脚本以提高系统性能。此外,文章还讨论了无需将程序加载到SRAM中的技术细节,为开发者提供了实用的参考和指导。 ... [详细]
  • Presto:高效即席查询引擎的深度解析与应用
    本文深入解析了Presto这一高效的即席查询引擎,详细探讨了其架构设计及其优缺点。Presto通过内存到内存的数据处理方式,显著提升了查询性能,相比传统的MapReduce查询,不仅减少了数据传输的延迟,还提高了查询的准确性和效率。然而,Presto在大规模数据处理和容错机制方面仍存在一定的局限性。本文还介绍了Presto在实际应用中的多种场景,展示了其在大数据分析领域的强大潜力。 ... [详细]
  • 本指南从零开始介绍Scala编程语言的基础知识,重点讲解了Scala解释器REPL(读取-求值-打印-循环)的使用方法。REPL是Scala开发中的重要工具,能够帮助初学者快速理解和实践Scala的基本语法和特性。通过详细的示例和练习,读者将能够熟练掌握Scala的基础概念和编程技巧。 ... [详细]
  • 本文详细介绍了如何在Java Web服务器上部署音视频服务,并提供了完整的验证流程。以AnyChat为例,这是一款跨平台的音视频解决方案,广泛应用于需要实时音视频交互的项目中。通过具体的部署步骤和测试方法,确保了音视频服务的稳定性和可靠性。 ... [详细]
  • Python 实战:异步爬虫(协程技术)与分布式爬虫(多进程应用)深入解析
    本文将深入探讨 Python 异步爬虫和分布式爬虫的技术细节,重点介绍协程技术和多进程应用在爬虫开发中的实际应用。通过对比多进程和协程的工作原理,帮助读者理解两者在性能和资源利用上的差异,从而在实际项目中做出更合适的选择。文章还将结合具体案例,展示如何高效地实现异步和分布式爬虫,以提升数据抓取的效率和稳定性。 ... [详细]
  • 投融资周报 | Circle 达成 4 亿美元融资协议,唯一艺术平台 A 轮融资超千万美元 ... [详细]
  • 基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析
    基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析 ... [详细]
  • 在托管C++中开发应用程序时,遇到了如何声明和操作字符串数组的问题。本文详细探讨了字符串数组在托管C++中的应用与实现方法,包括声明、初始化、遍历和常见操作技巧,为开发者提供了实用的参考和指导。 ... [详细]
  • 在处理大图片时,PHP 常常会遇到内存溢出的问题。为了避免这种情况,建议避免使用 `setImageBitmap`、`setImageResource` 或 `BitmapFactory.decodeResource` 等方法直接加载大图。这些函数在处理大图片时会消耗大量内存,导致应用崩溃。推荐采用分块处理、图像压缩和缓存机制等策略,以优化内存使用并提高处理效率。此外,可以考虑使用第三方库如 ImageMagick 或 GD 库来处理大图片,这些库提供了更高效的内存管理和图像处理功能。 ... [详细]
  • Eclipse JFace Text框架中IDocument接口的getNumberOfLines方法详解与编程实例 ... [详细]
  • 深入解析Spring Boot启动过程中Netty异步架构的工作原理与应用
    深入解析Spring Boot启动过程中Netty异步架构的工作原理与应用 ... [详细]
  • Java中高级工程师面试必备:JVM核心知识点全面解析
    对于软件开发人员而言,随着技术框架的不断演进和成熟,许多高级功能已经被高度封装,使得初级开发者只需掌握基本用法即可迅速完成项目。然而,对于中高级工程师而言,深入了解Java虚拟机(JVM)的核心知识点是必不可少的。这不仅有助于优化性能和解决复杂问题,还能在面试中脱颖而出。本文将全面解析JVM的关键概念和技术细节,帮助读者全面提升技术水平。 ... [详细]
  • 动态壁纸 LiveWallPaper:让您的桌面栩栩如生(第二篇)
    在本文中,我们将继续探讨如何开发动态壁纸 LiveWallPaper,使您的桌面更加生动有趣。作为 2010 年 Google 暑期大学生博客分享大赛 Android 篇的一部分,我们将详细介绍 Ed Burnette 的《Hello, Android》第三版中的相关内容,并分享一些实用的开发技巧和经验。通过本篇文章,您将了解到如何利用 Android SDK 创建引人入胜的动态壁纸,提升用户体验。 ... [详细]
  • Hadoop 2.6 主要由 HDFS 和 YARN 两大部分组成,其中 YARN 包含了运行在 ResourceManager 的 JVM 中的组件以及在 NodeManager 中运行的部分。本文深入探讨了 Hadoop 2.6 日志文件的解析方法,并详细介绍了 MapReduce 日志管理的最佳实践,旨在帮助用户更好地理解和优化日志处理流程,提高系统运维效率。 ... [详细]
author-avatar
你就夜沉思
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有