写点什么

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)。尤其是,我们并不能说这是系统代码的问题。

接下来本文将会介绍,对于这类崩溃如何进行识别、以及解决。



内存溢出(俗称 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,可多可少但不会差的远),它们用于操作系统内核相关的运作,如下图。



这里直接 总结重点 :

32 位的 App 在 32 位的手机 操作系统上使用 超过 3G 的内存 ,极大概率会发生 崩溃 ;

32 位的 App 在 64 位的手机 操作系统上使用 超过 4G 的内存 ,极大概率会发生 崩溃 ;

其中前者容易理解,1G 被系统吃了,就剩下了 3G;后者是因为 64 位手机上,系统是 64 位的,所以不需要跟 App 抢那 4G 空间。

至于 64 位 App,可用内存已经突破天际(所以开发 64 位 app 将会减少大量崩溃)……

需要注意,这里提到的内存,均为 虚拟内存 (可以回忆回忆学校学的操作系统知识,网上搜索瞅瞅)。

  1. 定位解决

这里需要用到的工具为应用性能监控全链路版(APMPlus),APMPlus 是字节跳动应用开发套件 MARS 下的性能监控产品,通过先进的数据采集与监控技术,为企业提供全链路的应用性能监控服务,解决企业对各端监控的需求。具备非侵入式监控、丰富的异常现场还原能力,助力企业提升异常问题排查与解决的效率、优化应用品质,以降低成本提高收入。

经过多年技术积累、亿级用户验证,APMPlus 集崩溃监控、上报、分析、归因于一体,可以轻松定位各种线上疑难杂症,更有超详细性能、卡顿、打点等全流程监控处理工具,覆盖近乎一切线上问题的处理。并拥有多个外部客户的实践,如:虎扑、作业帮、甄云科技等,为企业和开发者提供 一站式 APM 服务。

我们直接在 APMPlus 平台中查看崩溃,点击“Native 信息 -> Maps 详情”,查看虚拟内存占用。



一眼看出,这个内存占用明显接近上一节中提到的 内存占满的阈值 (32App 在 64 位设备上最多使用 4G 内存)!此时基本可以确认,该崩溃为内存占满导致的崩溃,即 OOM。

知道是 OOM 就完了?

再点一个按钮,直接告诉你怎么解决:“ Native 信息 -> Maps 智能归类 ”,查看 虚拟内存占用分布 。



我们可以看到,这里直接提示出三个地方占用的虚拟内存最多,分别是 Java runtime、Thread、Files;其中 Thread 占用最多 ,高达 2.59GB!

直接根据提示,逐级展开内存占用最多的条目:



立即破案: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 过多导致的崩溃 。



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



总结

本文提到的两种崩溃类型,本质上都是 系统、应用资源不足下 产生的。

资源不足实际上并不会直接导致崩溃,但是会 使某些系统调用返回出错 ,如 open 打开文件失败返回无效值、malloc 分配内存失败返回无效值等。这些返回的无效值如果在使用时未做合理容错判断,则会 引起如空指针等 这样的代码错误。

更多的崩溃问题归类及解析,将在应用性能监控全链路版(APMPlus)上及后续的文章中进行补充。

如果还未接入使用应用性能监控全链路版(APMPlus),也可以立刻开始进行免费试用,目前 APMPlus 面向新用户提供试用 30 天的限时免费服务。其中包含 App 监控、Web 监控、Server 监控、小程序监控,App 监控和 Web 监控各 500 万条事件量, Server 与小程序监控限时不限量。

更多产品信息,欢迎微信进群交流:


发布于: 刚刚阅读数: 2
用户头像

还未添加个人签名 2021.05.17 加入

字节跳动终端技术团队是大前端基础技术行业领军者,负责整个字节跳动的大前端基础设施建设,提升公司全产品线的性能、稳定性和工程效率,在移动端、Web、Desktop等各终端都有深入研究。

评论

发布
暂无评论
Android 资源溢出崩溃轻松解