Android 资源溢出崩溃轻松解

Android 资源溢出崩溃轻松解
文章图片

作者:字节跳动终端技术—李权飞
资源溢出是什么?
毫无疑问,应用的运行需要占用系统的资源。其中最为人所熟知的资源是内存,内存溢出便是耳熟能详的OOM。
常见的简单OOM一般可以通过堆栈来解决,如Java OOM,一部分可以直接从堆栈中看到哪里使用了多大内存导致了内存溢出,复杂一些的Java OOM,则可以使用其他分析工具来进行处理。但如果堆栈里看不出来呢?或者它不是Java崩溃呢?

java.lang.OutOfMemoryError: Failed to allocate a 3237132 byte allocation with 1612328 free bytes and 1574KB until OOM

比如下面这样的Native崩溃,堆栈全是系统堆栈,不花时间去研究就很难确定此崩溃的原因(事实上这个崩溃也是一个OOM)。尤其是,我们并不能说这是系统代码的问题。
接下来本文将会介绍,对于这类崩溃如何进行识别、以及解决。
Android 资源溢出崩溃轻松解
文章图片

内存溢出(俗称OOM)
如下case:
Signal 6(SIGABRT), Code -1(SI_QUEUE) #00 pc 000604de/apex/com.android.runtime/lib/bionic/libc.so (abort+165) #01 pc 0003606d/system/lib/libc++.so (abort_message+88) #02 pc 000361f1/system/lib/libc++.so (_ZL28demangling_terminate_handlerv+160) #03 pc 00045e4b/system/lib/libc++.so (_ZSt11__terminatePFvvE+2) #04 pc 00045653/system/lib/libc++.so (_ZN10__cxxabiv1L12failed_throwEPNS_15__cxa_exceptionE+12) #05 pc 000455b5/system/lib/libc++.so (__cxa_throw+72) #06 pc 00047c6d/system/lib/libc++.so (_Znwj+52) #07 pc 0000132b/system/lib/libbinderthreadstate.so (_ZNSt3__15dequeIN7android18IPCThreadStateBase9CallStateENS_9allocatorIS3_EEE19__add_back_capacityEv+186) #08 pc 0000120b/system/lib/libbinderthreadstate.so (_ZNSt3__15dequeIN7android18IPCThreadStateBase9CallStateENS_9allocatorIS3_EEE12emplace_backIJRS3_EEES8_DpOT_+52) #09 pc 0000115f/system/lib/libbinderthreadstate.so (_ZN7android18IPCThreadStateBase16pushCurrentStateENS0_9CallStateE+18) #10 pc 0003b901/system/lib/libbinder.so (_ZN7android14IPCThreadState14executeCommandEi+608) #11 pc 0003b5db/system/lib/libbinder.so (_ZN7android14IPCThreadState20getAndExecuteCommandEv+98) #12 pc 0003bb7b/system/lib/libbinder.so (_ZN7android14IPCThreadState14joinThreadPoolEb+38) #13 pc 00054de5/system/lib/libbinder.so (_ZN7android10PoolThread10threadLoopEv+12) #14 pc 0000d96f/system/lib/libutils.so (_ZN7android6Thread11_threadLoopEPv+210) #15 pc 00080eb5/system/lib/libandroid_runtime.so (_ZN7android14AndroidRuntime15javaThreadShellEPv+88) #16 pc 000ab3dd/apex/com.android.runtime/lib/bionic/libc.so (_ZL15__pthread_startPv+20) #17 pc 00061989/apex/com.android.runtime/lib/bionic/libc.so (__start_thread+30)

特征很明显,堆栈全是系统代码(/system/lib/xxx)。
这时候 无法一眼看出代码问题,那么就可以怀疑下内存原因。
  1. 崩溃原因
众所周知,32位CPU寻址范围最大可以到2的32次方 = 4GB,其实就是 32位操作系统 最大支持 4G内存。
如果你试图装过系统就会明白,32位操作系统下,内存不可能达到4G以上,一般会是3G左右。
为什么是3G?因为还有 1G被系统吃掉 了(不一定真的是1G,可多可少但不会差的远),它们用于操作系统内核相关的运作,如下图。
Android 资源溢出崩溃轻松解
文章图片

这里直接 总结重点 :
32位的App 在 32位的手机 操作系统上使用 超过3G的内存 ,极大概率会发生 崩溃 ;
32位的App 在 64位的手机 操作系统上使用 超过4G的内存 ,极大概率会发生 崩溃 ;
其中前者容易理解,1G被系统吃了,就剩下了3G;后者是因为64位手机上,系统是64位的,所以不需要跟App抢那4G空间。
【Android 资源溢出崩溃轻松解】至于64位App,可用内存已经突破天际(所以开发64位app将会减少大量崩溃)……
需要注意,这里提到的内存,均为 虚拟内存 (可以回忆回忆学校学的操作系统知识,网上搜索瞅瞅)。
  1. 定位解决
这里需要用到的工具为字节跳动企业级技术服务平台火山引擎下的 应用性能监控全链路版 ,是终端技术团队基于字节跳动内部抖音、今日头条、飞书等多个超大规模用户量级移动App的多年沉淀和积累后的完全自研的应用性能监控产品。
经过多年技术积累、亿级用户验证,应用性能监控全链路版 集崩溃监控、上报、分析、归因 于一体,可以轻松定位各种线上疑难杂症,更有超详细 性能、卡顿、打点 等全流程监控处理工具,覆盖近乎一切线上问题的处理。并拥有多个外部客户的实践,如:虎扑、作业帮、甄云科技等,为企业和开发者提供 一站式APM服务。
我们直接在 应用性能监控全链路版 中查看崩溃,点击“ Native 信息 -> Maps详情 ”,查看 虚拟内存占用 。
Android 资源溢出崩溃轻松解
文章图片

一眼看出,这个内存占用明显接近上一节中提到的 内存占满的阈值 (32App在64位设备上最多使用4G内存)!此时基本可以确认,该崩溃为内存占满导致的崩溃,即OOM。
知道是OOM就完了?
再点一个按钮,直接告诉你怎么解决:“ Native 信息 -> Maps智能归类 ”,查看 虚拟内存占用分布 。
Android 资源溢出崩溃轻松解
文章图片

我们可以看到,这里直接提示出三个地方占用的虚拟内存最多,分别是Java runtime、Thread、Files;其中 Thread占用最多 ,高达2.59GB!
直接根据提示,逐级展开内存占用最多的条目:
Android 资源溢出崩溃轻松解
文章图片

立即破案:doTestThread线程过多导致虚拟内存占满!接下来只需要去代码里看,哪里创建的这个线程,便可进行问题解决。
类似的,一旦在崩溃中发现Maps智能归类中给出的任意一个条目过高,都可以确认出OOM的原因;假如发现Files条目占用内存达到了2G,那么只需根据内存名即可确认什么文件占用内存多,从而进行问题定位解决。
其中由于 “ Java runtime”条目占用起点较高 ,其内包含Java堆内存等虚拟机自用区域,基本上固定占用1G上下,且一般情况下其占用不会受我们的代码控制,所以需要注意不要被它混淆了视线, 优先关注其他条目 即可。
另外,Thread内存占用过多且需要查看线程的详细信息时,可以在“Native信息 -> 线程状态”中查看。
注:不同App下,虚拟内存分布的结果都有不同,具体分析需联系自身App正常情况下的内存分布来确认问题。
  1. 内存类型简要解释
平台中当前的内存分类方式:
  • Java runtime:安卓系统Java虚拟机占用,一般App默认会占用1G以上,可降低关注优先级
  • Native Heap:C代码使用的堆内存大小,如malloc调用分配的内存等,都会在这里体现;
  • Thread:线程使用的内存大小,默认情况下每个线程启动后(Java、Native均如此)便会占用1M内存
  • Files:映射入内存中的文件,一般由C代码中调用mmap直接加载文件到内存里,Java中使用FileInputStream不会在这里体现
  • Devices:设备相关内存使用
  • nameless:部分没有名字的未知内存使用
  • Other:其他未识别内存
FD溢出
如下case:
Signal 6(SIGABRT), Code -1(SI_QUEUE) abort message: 'Could not make wake event fd: Too many open files' #00 pc 000604de/apex/com.android.runtime/lib/bionic/libc.so (abort+165) #01 pc 00005a95/system/lib/liblog.so (__android_log_assert+176) #02 pc 000100bf/system/lib/libutils.so (_ZN7android6LooperC2Eb+218) #03 pc 000d3c51/system/lib/libandroid_runtime.so (_ZN7android18NativeMessageQueueC1Ev+112) #04 pc 000d424d/system/lib/libandroid_runtime.so (_ZN7androidL34android_os_MessageQueue_nativeInitEP7_JNIEnvP7_jclass+12) #05 pc 002920e7/system/framework/arm/boot-framework.oat (art_jni_trampoline+94) #06 pc 000d7bc5/apex/com.android.runtime/lib/libart.so (art_quick_invoke_stub_internal+68) ...... #30 pc 003afb7f/apex/com.android.runtime/lib/libart.so (_ZN3art6Thread14CreateCallbackEPv+1018) #31 pc 000ab3dd/apex/com.android.runtime/lib/bionic/libc.so (_ZL15__pthread_startPv+20) #32 pc 00061989/apex/com.android.runtime/lib/bionic/libc.so (__start_thread+30)

同样的,堆栈基本无意义,但有一句看起来能看懂的“Too many open files”。
  1. 崩溃原因
FD即文件描述符(File Descriptor),打开一个文件就占用一个。
看起来没什么的,大家读写文件都是常规操作,一个App产生千八百个文件不过分吧。
但是,系统会 限制单个App打开的 FD 个数 !
该数字在部分低版本安卓机上 一般为1024 ,也就是你打开1024个FD后,就不能再打开了,有时候就会因此 产生崩溃 。
  1. 定位解决
直接点开“ Native 信息 -> FD 归类 ”,来确认是不是 FD 过多导致的崩溃 。
Android 资源溢出崩溃轻松解
文章图片

很明显,确实可以看到使用的FD过多,达到了3万以上。向下滚动可以直接看到App在运行时到底打开了哪些文件,只要找到打开的文件名,便能轻松解决此类崩溃。
Android 资源溢出崩溃轻松解
文章图片

总结
本文提到的两种崩溃类型,本质上都是 系统、应用资源不足下 产生的。
资源不足实际上并不会直接导致崩溃,但是会 使某些系统调用返回出错 ,如open打开文件失败返回无效值、malloc分配内存失败返回无效值等。这些返回的无效值如果在使用时未做合理容错判断,则会 引起如空指针等 这样的代码错误。
更多的崩溃问题归类及解析,将在应用性能监控全链路版(APMPlus)上及后续的文章中进行补充。
如果还未接入使用应用性能监控全链路版(APMPlus),也可以立刻开始进行免费试用,目前 APMPlus面向新用户提供试用30 天的限时免费服务。其中包含 App 监控、Web 监控、Server 监控、小程序监控,App 监控和 Web 监控各500 万条事件量, Server 与小程序监控限时不限量。
更多产品信息,欢迎微信进群交流:
Android 资源溢出崩溃轻松解
文章图片

    推荐阅读