Android OOM案例分析

在Android(Java)开发中,基本都会遇到java.lang.OutOfMemoryError(本文简称OOM),这种错误解决起来相对于一般的Exception或者Error都要难一些,主要是由于错误产生的root cause不是很显而易见。由于没有办法能够直接拿到用户的内存dump文件,如果错误发生在线上的版本,分析起来就会更加困难。本文从一个具体的案例切入,介绍OOM分析的思路及相关工具的使用。

案例背景

在美团App 7.4~7.7版本期间,美食业务的OOM数量居高不下,远高于历史水平,主要都是DECODE本地的资源出错。

图中OOM数量为各版本发版后第一个月的统计量,包含新发版本及历史版本。对比了同时期其他业务的情况,也有类似OOM。由于美食业务的访问量占美团App的比重较大,因此,OOM的数量相对其他业务也多一些。

思路方案

在问题较为严重的7.6~7.7版本期间,团队对OOM频现的原因有过各种猜测。笔者怀疑过是否是业务上某些修改引起的,例如头图尺寸变大,或者是由页面模块加载方式引起的等等。但这些与OOM问题出现的时间并不吻合。其次也怀疑过是否由某些ROM的Bug导致,但此推断缺乏有力的证据支撑。因此,要找到OOM的root cause,根本途径还是找到谁占的内存最多,然后再根据具体case具体分析,为什么占了这么多。

采集用户手机内存信息

要分析内存的占用,需要内存的dump文件,但是dump文件一般都比较大,让用户配合上传dump文件不合适。所以希望能够运行时采集一些内存的特征然后随着crash日志上报上来。当用户发生OOM时,dump出用户的内存,然后基于com.squareup.haha:haha:2.0.3分析,得到一些关键数据(内存占用最多的实例及所占比例等)。但这个方案很快就被证明是不可行的。主要基于下面几个原因: - 需要引入新的库。 - dump和分析内存都很耗时,效率难以接受。 - OOM时内存已经几乎耗尽,再加载内存dump文件并分析会导致二次OOM,得不偿失。

模拟复现OOM

采集用户手机内存信息的方案不可行,那么只能采取复现用户场景的方式。由于发生OOM时,用户操作路径的不确定性,无法精确复现线上的OOM,因此采取模拟复现的方式,最终发生OOM时的栈信息基本一致即可。为了能够尽量模拟用户发生OOM的场景,需要基本条件基本一致,即用户使用的手机的各种相关参数。

挖掘OOM特征

分析7.4以来的OOM,列出发生OOM的机器的特征,主要是内存和分辨率,适当考虑其它因素例如系统版本。

| 机型 | 内存 | 分辨率 | OS | stack log | | – | – | – | – | – | | OPPO N1(T/W) | 2G | 1920*1080 | 4.2.2 | java.lang.OutOfMemoryError
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)| | HM 2LTE-CMCC | 1G | 1280*720 | 4.4.4 | java.lang.OutOfMemoryError
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method) | | Newman CM810 | 2G | 1920*1080 | 4.4.4 | java.lang.OutOfMemoryError
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method) | | LGL22 | 2G | 1830*1080 | 4.2.2 | java.lang.OutOfMemoryError
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method) | | OPPO X909 | 2G | 1920*1080 | 4.2.2 | java.lang.OutOfMemoryError
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method) | | Lenovo K900 | 2G | 1920*1080 | 4.2.2 | java.lang.OutOfMemoryError
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method) | | GiONEE E6 | 2G | 1920*1080 | 4.2.1 | java.lang.OutOfMemoryError
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method) |

这些特征可以总结为:内存一般,分辨率偏高,OOM的堆栈log基本一致。其中,OPPO N1(T/W)上所发生的OOM比重较高,约为65%,因此选定这款机器作为复现OOM的机器。

关键数据(内存dump文件)

需要复现OOM然后获取内存dump。思路是采取内存压力测试,让问题暴露的快速且充分。具体方案为: - 选取图片资源多且较为复杂的页面,比如美食的POI详情页。 - 加载30次该页面,为了增加OOM的几率,30个POI页面的ID是不同的。

OOM发生后,使用Android Studio自带的Android Monitor dump出HPROF文件,然后使用SDK中的hprof-conv(位于sdk_root/platform-tools)工具转换为标准的Java堆转储文件格式,这样可以使用MAT(Eclipse Memory Analyzer)继续分析。

切到histogram视图,按shadow heap降序排列。

选取byte数组,右击->list objects->with incoming references,降序排列可以看到有很多大小一致的byte[]实例。

右击其中一个数组->Path to GC Roots-> exclude xxx references

如上图所示,这些byte[]都是系统的EdgeEffect的drawable所持有,drawable对应的bitmap占用的空间为1566 * 406 * 4 = 2543184,与byte数组的大小一致。

再看另外一个:

这些byte[]是被App的一个背景图所持有,如下图:

通过ImageView的ID(如图)及build目录下的R.txt反查可知该ImageView的ID名称,即可知其设置的背景图的大小为720 * 200(xhdpi),加载到内存并考虑density,size刚好是1080 * 300 * 4 = 1296000,与byte数组大小一致。

数据分析

为什么会出现这些大小一致的byte数组,或者说,为什么会创建多份EdgeEffect的drawable?查看EdgeEffect的源码(4.2.2)可知,其drawable成员也是通过Resources.getDrawable系统调用获取的。

/*** Construct a new EdgeEffect with a theme appropriate for the provided context.* @param context Context used to provide theming and resource information for the EdgeEffect*/
public EdgeEffect(Context context) {final Resources res = context.getResources();mEdge = res.getDrawable(R.drawable.overscroll_edge);mGlow = res.getDrawable(R.drawable.overscroll_glow);******mMinWidth = (int) (res.getDisplayMetrics().density * MIN_WIDTH + 0.5f);mInterpolator = new DecelerateInterpolator();
}

ImageView(View)获取background对应的drawable的过程类似。

for (int i = 0; i < N; i++) {int attr = a.getIndex(i);switch (attr) {case com.android.internal.R.styleable.View_background:background = a.getDrawable(attr); // TypedArray.getDrawablebreak;******}
}

不论是Resources.getDrawable还是TypedArray.getDrawable,最终都会调用Resources.loadDrawable。继续看Resources.loadDrawable的源码,发现的确是使用了缓存。对于同一个drawable资源,系统只会加载一次,之后都会从缓存去取。

既然drawable的加载机制并没有问题,那么drawable所在的缓存实例或者获取drawable的Resources实例是否是同一个呢?通过下面的代码,打印出每个Activity的Resources实例及Resources实例的drawable cache。

//noinspection unchecked
LongSparseArray<WeakReference<Drawable.ConstantState>> cache = (LongSparseArray<WeakReference<Drawable.ConstantState>>) Hack.into(Resources.class).field("mDrawableCache").get(getResources());
Object appCache = Hack.into(Resources.class).field("mDrawableCache").get(getApplication().getResources());
Log.e("oom", "Resources: {application=" + getApplication().getResources() + ", activity=" + getResources() + "}");
Log.e("oom", "Resources.mDrawableCache: {application=" + appCache + ", activity=" + cache + "}"); 

这也进一步解释了另外一个现象,即这些大小相同的数组的个数基本和启动Activity的数量成正比。

通过数据分析可知,这些drawable之所以存在多份,是因为其所在的Resources实例并不是同一个。进一步debug可知,Resources实例存在多个的原因是开启了标志位sCompatVectorFromResourcesEnabled。 虽然最终造成OOM突然增多的原因只是开启一个标志位,但是这也告诫大家阅读API文档的重要性,其实很多时候API的使用说明已经明确告知了使用的限制条件甚至风险。

7.8版本关闭了此标志,发版后第一个月的OOM数量(包含历史版本)为153,如下图。

其中新版本发生的OOM数量为22。

总结

对于线上出现的OOM,如何分析和解决可以大致分为三个步骤:

  1. 充分挖掘特征。在挖掘特征时,需要多方面考虑,此过程更多的是猜测怀疑,所以可能的方面都要考虑到,包括但不限于代码改动、机器特征、时间特征等,必要时还需要做一定的统计分析。
  2. 根据掌握的特征寻找稳定的复现的途径。一般需要做内存压力测试,这样比较容易达到OOM的临界值,只是简单的一些正常操作难以触发OOM。
  3. 获取可分析的数据(内存dump文件)。利用MAT分析dump文件,MAT可以方便的按照大小排序实例,可以查看某些实例到GC ROOT的路径。

作者简介

军慧,美团点评Android高级工程师,2015年加入原美团,负责美团点评到店餐饮业务美团Android App的开发工作。

到店餐饮技术部交易与信息技术中心,负责美团点评美食用户端业务,服务于数以亿计用户,通过更好的榜单、真实的评价和完善的信息为用户提供更好的决策支持,致力于提升用户体验;同时承载所有餐饮商户端线上流量,为餐饮商户提供多种营销工具,提升餐饮商户营销效率,最终达到让国人“Eat Better、Live Better”的美好愿景!我们的团队包含且不限于Android、iOS、FE、Java、PHP等技术方向,已完备覆盖前后端技术栈。只要你来,就能点亮全栈开发技能树。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/477105.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NAACL最佳方法论文:课本上的A*搜索算法可以提升文本生成效果!

文 | Yimin_饭煲相信大多数学习过人工智能课程的读者&#xff0c;当听到算法的时候&#xff0c;都会有一种既熟悉又陌生的感觉。说算法熟悉&#xff0c;是因为一听到这个算法&#xff0c;就想起那本厚厚的《人工智能——一种现代的方法》&#xff0c;想起这个算法似乎是人工智能…

LeetCode 6. Z 字形变换(找规律)

1. 题目 将一个给定字符串根据给定的行数&#xff0c;以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 “LEETCODEISHIRING” 行数为 3 时&#xff0c;排列如下&#xff1a; L C I R E T O E S I I G E D H N之后&#xff0c;你的输出需要从左往右逐行…

美团点评移动网络优化实践

本文根据第16期美团点评技术沙龙“移动开发实践&#xff08;上海站&#xff09;”演讲内容整理而成。 第18期沙龙&#xff1a;高可用系统背后的基础架构&#xff08;3月25日&#xff09;火热来袭&#xff01;快快点击报名吧。 网络优化对于App产品的用户体验至关重要&#xff0…

我终于逃离了互联网,却陷入了迷茫

大家好&#xff0c;我是卖萌酱。昨天跟一位成功逃离互联网的好姐妹小A约了个饭&#xff0c;这位姐妹的早期经历可以说是略带传奇色彩了&#xff0c;过程却比较崎岖&#xff0c;结局心情复杂&#xff0c;但却对普通人来说却很有启发意义。经过小A允许&#xff0c;卖萌酱将小A的故…

LintCode 633. 寻找重复的数(这个题要复习)

1. 题目 给出一个数组 nums 包含 n 1 个整数&#xff0c;每个整数是从 1 到 n (包括边界)&#xff0c;保证至少存在一个重复的整数。假设只有一个重复的整数&#xff0c;找出这个重复的数。 样例 1: 输入: [5,5,4,3,2,1] 输出: 5样例 2: 输入: [5,4,4,3,2,1] 输出: 4注意事项…

业务赋能利器之外卖特征档案

应用背景及现状 美团外卖业务自2013年9月启动至今已运营三年时间。截至2016年12月&#xff0c;美团点评整个外卖平台的日订单超过900万。从发展速度和体量上看&#xff0c;外卖业务仍处在迅猛发展的上升期。与早期飞速增长的状态相比&#xff0c;随着规模的不断扩大&#xff0c…

训练双塔检索模型,可以不用query-doc样本了?明星机构联合发文

文 | QvQ对于开放域检索式QA系统而言&#xff0c;其本质是计算question和doc的本文相似度&#xff0c;而作为老生常谈的文本相似度问题&#xff0c;有监督方法的性能历来是要好于无监督算法的。今天要介绍的文章&#xff0c;反其道而行之&#xff0c;不仅采用了无监督算法&…

MGW——美团点评高性能四层负载均衡

本文整理自美团点评技术沙龙第14期&#xff1a;美团背后的故事&#xff0d;你不知道的美团云。 美团点评技术沙龙由美团点评技术团队主办&#xff0c;每月一期。每期沙龙邀请美团点评及其他互联网公司的技术专家分享来自一线的实践经验&#xff0c;覆盖各主要技术领域。 目前沙…

剑指Offer - 面试题36. 二叉搜索树与双向链表(中序循环/递归)

1. 题目 输入一棵二叉搜索树&#xff0c;将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点&#xff0c;只能调整树中节点指针的指向。 特别地&#xff0c;我们希望可以就地完成转换操作。当转化完成以后&#xff0c;树中节点的左指针需要指向前驱&#…

知乎高赞:拼多多和国家电网,选哪个?

源 | 知乎、AINLP最近在知乎上看到一个关于职业选择的问题&#xff0c;虽然都是老生常谈的话题了&#xff0c;但是几个知友的回答获得非常多的赞同&#xff0c;拿来和大家分享一下。知乎上有人问本人是某top3本硕&#xff0c;EE专业&#xff0c;秋招拿到了老家不差的地级市国网…

大众点评订单系统分库分表实践

原大众点评的订单单表早就已经突破两百G&#xff0c;由于查询维度较多&#xff0c;即使加了两个从库&#xff0c;优化索引&#xff0c;仍然存在很多查询不理想的情况。去年大量抢购活动的开展&#xff0c;使数据库达到瓶颈&#xff0c;应用只能通过限速、异步队列等对其进行保护…

NLP未来,路在何方?从学术前沿和业界热点谈起

近两年&#xff0c;人工智能的应用越来越“卷”了&#xff0c;每隔一段时间就会出现一个让人大呼“respect”的技术。AI好像也更加懂人类&#xff0c;越来越接近“人的智能”。就好比今年高考期间被各大科技媒体反复拿来讲的AI高考的案例。如今的“AI做题家”不光能参加高考&am…

剑指Offer - 面试题46. 把数字翻译成字符串(DP)

1. 题目 给定一个数字&#xff0c;我们按照如下规则把它翻译为字符串&#xff1a; 0 翻译成 “a” &#xff0c; 1 翻译成 “b”&#xff0c;……&#xff0c; 11 翻译成 “l”&#xff0c;……&#xff0c; 25 翻译成 “z”。 一个数字可能有多个翻译。请编程实现一个函数&a…

美团数据库运维自动化系统构建之路

本文整理自美团点评技术沙龙第10期&#xff1a;数据库技术架构与实践。 美团点评技术沙龙由美团点评技术团队主办&#xff0c;每月一期。每期沙龙邀请美团点评及其它互联网公司的技术专家分享来自一线的实践经验&#xff0c;覆盖各主要技术领域。 目前沙龙会分别在北京、上海和…

推荐一个开源的炼丹神器MegPeak!算法工程师的仪表盘

在算力需求爆炸的大背景下&#xff0c;如何发挥出已有硬件的最大算力变得非常重要&#xff0c;直观一点是&#xff1a;我们需要对现有算法针对特定的处理器进行极致的性能优化&#xff0c;尽量满足目前AI算法对算力的高要求。为了能够做到极致的性能优化&#xff0c;我们可能的…

LintCode 390. 找峰值 II

1. 题目 给定一个整数矩阵 A, 它有如下特性: 相邻的整数不同矩阵有 n 行 m 列。对于所有的 i < n, 都有 A[i][0] < A[i][1] && A[i][m - 2] > A[i][m - 1]对于所有的 j < m, 都有 A[0][j] < A[1][j] && A[n - 2][j] > A[n - 1][j] 我们定…

Node.js Stream - 实战篇

前面两篇&#xff08;基础篇和进阶篇&#xff09;主要介绍流的基本用法和原理&#xff0c;本篇从应用的角度&#xff0c;介绍如何使用管道进行程序设计&#xff0c;主要内容包括&#xff1a; 管道的概念Browserify的管道设计Gulp的管道设计两种管道设计模式比较实例所谓“管道”…

我们的Web3创业项目,黄了

文 | 邬宇琛源 | 投资界PEdaily这是今年VC圈最具争议的赛道&#xff0c;如今也可能是熄灭最快的赛道。三个月&#xff0c;换了三个方向。今年春天&#xff0c;北京某出海互联网公司在内部筹划起一个新的项目&#xff0c;项目直指风口——Web3。毫无Web3经验的互联网运营经理吴欣…

决策树(Decision Tree,DT)

文章目录1. 决策树模型与学习2. 特征选择2.1 特征选择Python代码3. 决策树的生成3.1 Python代码4. 决策树的剪枝5. CART 算法6. sklearn 例子6.1 书上贷款例子6.2 鸢尾花 及 决策树可视化附. 本文完整代码决策树&#xff08;decision tree&#xff09;是一种基本的分类与回归方…

使用模板快速编写测试用例

在高速发展的互联网公司&#xff0c;由于产品的开发迭代太快&#xff0c;产品测试经常遇到以下几个问题&#xff1a; 1. 如何在快速的产品开发迭代中迅速地完成对产品功能的测试&#xff1f; 2. 面对用户众多、环境多样&#xff0c;如何尽可能地测试全面&#xff1f; 3. 公司扩…