资源溢出是什么?
毫无疑问,应用的运行需要占用系统的资源。其中最为人所熟知的资源是内存,内存溢出便是耳熟能详的OOM。
常见的简单OOM一般可以通过堆栈来解决,如Java OOM,一部分可以直接从堆栈中看到哪里使用了多大内存导致了内存溢出,复杂一些的Java OOM,则可以使用其他分析工具来进行处理。但如果堆栈里看不出来呢?或者它不是Java崩溃呢?
比如下面这样的Native崩溃,堆栈全是系统堆栈,不花时间去研究就很难确定此崩溃的原因(事实上这个崩溃也是一个OOM)。尤其是,我们并不能说这是系统代码的问题。
接下来本文将会介绍,对于这类崩溃如何进行识别、以及解决。
内存溢出(俗称OOM)
如下case:
特征很明显,堆栈全是系统代码(/system/lib/xxx)。
这时候无法一眼看出代码问题,那么就可以怀疑下内存原因。
-
崩溃原因
众所周知,32位CPU寻址范围最大可以到2的32次方 = 4GB,其实就是32位操作系统最大支持4G内存。
如果你试图装过系统就会明白,32位操作系统下,内存不可能达到4G以上,一般会是3G左右。
为什么是3G?因为还有1G被系统吃掉了(不一定真的是1G,可多可少但不会差的远),它们用于操作系统内核相关的运作,如下图。
这里直接总结重点:
32位的App在32位的手机操作系统上使用超过3G的内存,极大概率会发生Native崩溃;
32位的App在64位的手机操作系统上使用超过4G的内存,极大概率会发生Native崩溃;
其中前者容易理解,1G被系统吃了,就剩下了3G;后者是因为64位手机上,系统是64位的,所以不需要跟App抢那4G空间。
至于64位App,可用内存已经突破天际(所以开发64位app将会减少大量Native崩溃)……
几种主流内存占用类型,可以文末会给出一个总结。
需要注意,这里提到的内存,均为虚拟内存(可以回忆回忆学校学的操作系统知识,网上搜索瞅瞅)。
-
定位解决
这里需要用到的工具为应用性能监控全链路版(APMPlus),APMPlus是字节跳动应用开发套件MARS下的性能监控产品,通过先进的数据采集与监控技术,为企业提供全链路的应用性能监控服务,解决企业对各端监控的需求。具备非侵入式监控、丰富的异常现场还原能力,助力企业提升异常问题排查与解决的效率、优化应用品质,以降低成本提高收入。
经过多年技术积累、亿级用户验证,APMPlus 集崩溃监控、上报、分析、归因于一体,可以轻松定位各种线上疑难杂症,更有超详细性能、卡顿、打点等全流程监控处理工具,覆盖近乎一切线上问题的处理。并拥有多个外部客户的实践,如:虎扑、作业帮、甄云科技等,为企业和开发者提供 一站式APM服务。
我们直接在A PMPlus 平台中查看崩溃,点击“Native 信息 -> Maps详情”,查看虚拟内存占用。
一眼看出,这个内存占用明显接近上一节中提到的内存占满的阈值(32App在64位设备上最多使用4G内存)!此时基本可以确认,该崩溃为内存占满导致的Native崩溃,即Native OOM。
知道是Native OOM就完了?
再点一个按钮,直接告诉你怎么解决:“Native 信息 -> Maps智能归类”,查看虚拟内存占用分布。
我们可以看到,这里直接提示出三个地方占用的虚拟内存最多,分别是Java runtime、Thread、Files;其中Thread占用最多,高达2.59GB!
直接根据提示,逐级展开内存占用最多的条目:
立即破案:doTestThread线程过多导致虚拟内存占满!接下来只需要去代码里看,哪里创建的这个线程,便可进行问题解决。
类似的,一旦在崩溃中发现Maps智能归类中给出的任意一个条目过高,都可以确认出Native OOM的原因;假如发现Files条目占用内存达到了2G,那么只需根据内存名即可确认什么文件占用内存多,从而进行问题定位解决。
其中由于 “ Java runtime”条目占用起点较高,其内包含Java堆内存等虚拟机自用区域,基本上固定占用1G上下,且一般情况下其占用不会受我们的代码控制,所以需要注意不要被它混淆了视线,优先关注其他条目即可。
另外,Thread内存占用过多且需要查看线程的详细信息时,可以在“Native信息 -> 线程状态”中查看。
注:不同App下,虚拟内存分布的结果都有不同,具体分析需联系自身App正常情况下的内存分布来确认问题。
-
内存类型简要解释
ApmInsight平台当前的内存分类方式:
- Java runtime:安卓系统Java虚拟机占用,一般App默认会占用1G以上,可降低关注优先级
- Native Heap:C代码使用的堆内存大小,如malloc调用分配的内存等,都会在这里体现;
- Thread:线程使用的内存大小,默认情况下每个线程启动后(Java、Native均如此)便会占用1M内存
- Files:映射入内存中的文件,一般由C代码中调用mmap直接加载文件到内存里,Java中使用FileInputStream不会在这里体现
- Devices:设备相关内存使用
- nameless:部分没有名字的未知内存使用
- Other:其他未识别内存
FD溢出
如下case:
同样的,堆栈基本无意义,但有一句看起来能看懂的“Too many open files”。
-
崩溃原因
FD即文件描述符(File Descriptor),打开一个文件就占用一个。
看起来没什么的,大家读写文件都是常规操作,一个App产生千八百个文件不过分吧。
但是,系统会限制单个App打开的 FD 个数!
该数字在部分低版本安卓机上一般为1024,也就是你打开1024个FD后,就不能再打开了,有时候就会因此产生Native崩溃。
-
定位解决
直接点开“Native 信息 -> FD 归类”,来确认是不是FD 过多导致的崩溃。
很明显,确实可以看到使用的FD过多,达到了3万以上。向下滚动可以直接看到App在运行时到底打开了哪些文件,只要找到打开的文件名,便能轻松解决此类崩溃。
总结
本文提到的两种崩溃类型,本质上都是系统、应用资源不足下产生的。
资源不足实际上并不会直接导致崩溃,但是会使某些系统调用返回出错,如open打开文件失败返回无效值、malloc分配内存失败返回无效值等。这些返回的无效值如果在使用时未做合理容错判断,则会引起如空指针等这样的代码错误。
更多的崩溃问题归类及解析,将在应用性能监控全链路版(APMPlus)上及后续的文章中进行补充。
如果还未接入使用应用性能监控全链路版(APMPlus),也可以立刻开始进行免费试用,目前 APMPlus面向新用户提供试用30 天的限时免费服务。其中包含 App 监控、Web 监控、Server 监控、小程序 监 控,App 监控和 Web 监控各500 万条事件量, Server 与小程序监控限时不限量。