Android进阶性能调优;不可思议的OOM,搞懂这些直接来阿里入职

后面分析线程创建过程中会发现过程中涉有及到文件描述符。

2 . /proc/sys/kernel 中描述的限制

这些限制中与线程相关的是 /proc/sys/kernel/threads-max,规定了每个进程创建线程数目的上限,所以线程创建导致 OOM 的原因也有可能与这个限制相关。

3.3验证

下面对上述的推断进行验证,分两步:本地验证和线上验收。

  • 本地验证:在本地验证推断,试图复现与图 [2-4]OOM 一与图 [2-5]OOM 二所示错误消息一致的 OOM
  • 线上验收:下发插件,验收线上用户 OOM 时确实是由于上面的推断的原因导致的

本地验证

实验一: 触发大量网络连接(每个连接处于独立的线程中)并保持,每打开一个 socket 都会增加一个 fd(/proc/pid/fd 下多一项)

注:不只有这一种增加 fd 数的方式,也可以用其他方法,比如打开文件,创建 handlerthread 等等

  • 实验预期:当进程 fd 数(可以通过 ls /proc/pid/fd | wc -l 获得)突破 /proc/pid/limits 中规定的 Max open files 时,产生 OOM;
  • 实验结果:当 fd 数目到达 /proc/pid/limits 中规定的 Max open files 时,继续开线程确实会导致 OOM 的产生。

错误信息及堆栈如下:

图 3-5 FD 数超限导致 OOM 的详细信息

可以看出,此 OOM 发生时的错误信息确与线上发现的 OOM 一的“Could not allocate JNI Env” 吻合,因此线上上报的 OOM 一 可能 就是由 FD 数超限导致的,不过最终确定需要到线上进行验证 (下一小节)。此外从 ART 虚拟机的 Log 中看出,还有一个关键的信息 “ art: ashmem_create_region failed for ‘indirect ref table’: Too many open files”,后面会用于问题定位及解释。

实验二:创建大量的空线程(不做任何事情,直接 sleep)

  • 实验预期:
  • 当线程数(可以在/proc/pid/status 中的threads项实时查看)超过/proc/sys/kernel/threads-max 中规定的上限时产生 OOM 崩溃。
  • 实验结果:
  • 在 Android7.0 及以上的华为手机(EmotionUI_5.0 及以上)的手机产生 OOM,这些手机的线程数限制都很小 (应该是华为 rom 特意修改的 limits),每个进程只允许最大同时开 500 个线程,因此很容易复现了。

OOM 时错误信息如下:

图 3-6 线程数超限导致的 OOM 详细信息

可以看出 错误信息与我们线上遇到的 OOM 二吻合:“pthread_create (1040KB stack) failed: Out of memory” 另外 ART 虚拟机还有一个关键 Log:“pthread_create failed: clone failed: Out of memory”,后面会用于问题定位及解释。

1 . 其他 Rom 的手机线程数的上限都比较大,不容易复现上述问题。但是,对于 32 位的系统,当进程的逻辑地址空间不够的时候也会产生 OOM,每个线程通常需要 mapp 1MB 左右的 stack 空间(stack 大小可以自行设置),32 为系统进程逻辑地址 4GB,用户空间少于 3GB。逻辑地址空间不够(已用逻辑空间地址可以查看 /proc/pid/status 中的 VmPeak/VmSize 记录),此时创建线程产生的 OOM 具有如下信息:

图 3-7 逻辑地址空间占满导致的 OOM

线上验收及问题解决

本地尝试复现的 OOM 错误信息中图 [3-5] 与线上 OOM 一情况比较吻合,图 [3-6] 与线上 OOM 二的情况比较吻合,但线上的 OOM 一真的时 FD 数目超限,OOM 二真的是由于华为手机线程数超限的原因导致的吗?最终确定还需要取线上设备的数据进行验证。

验证方法:

下发插件到线上用户,当 Thread.UncaughtExceptionHandler 捕获到OutOfMemoryError 时记录 /proc/pid 目录下的如下信息:

1. /proc/pid/fd 目录下文件数 (fd 数)

2. /proc/pid/status 中 threads 项(当前线程数目)

3. OOM 的日志信息(出了堆栈信息还包含其他的一些 warning 信息

线上 OOM 一验证

发生 OOM 一的线上设备中采集到的信息:

1. /proc/pid/fd 目录下文件数与 /proc/pid/limits 中的 Max open files 数目持平,证明 FD 数目已经满了;

2. 崩溃时日志信息与图 [3-5] 基本一致;

由此,证明 线上的 OOM 一确实是由于 FD 数目过多导致的 OOM,推断验证成功。

OOM 一的定位与解决:

最终原因是 App 中使用的长连接库再某些时候会有瞬时发出大量 http 请求的 bug(导致 FD 数激增),已修复。

线上 OOM 二验证 集中在华为系统的 OOM 二崩溃时收集到的信息样例如下,(收集的样例中包含的 devicemodel 有 VKY-AL00,TRT-AL00A,BLN-AL20,BLN-AL10,DLI-AL10,TRT-TL10,WAS-AL00 等):

1. /proc/pid/status 中 threads 记录全部到达上限:Threads: 500;

2. 崩溃时日志信息与图 [3-6] 基本一致;

推断验证成功,即 线程数受限导致创建线程时 clone failed 导致了线上的 OOM 二

OOM 二的定位与解决:

关于 App 业务代码中的问题还在定位修复中。

3.4解释

下面从代码分析本文描述的 OOM 是怎么发生的,首先线程创建的简易版流程图如下所示:

图 3-8 线程创建流程

上图中,线程创建大概有两个关键的步骤:

  • 第一列中的 创建线程私有的结构体 JNIENV(JNI 执行环境,用于 C 层调用 Java 层代码)
  • 第二列中的 调用 posix C 库的函数 pthread_create 进行线程创建工作

下面对流程图中关键节点(图中有标号的)进行说明:

1. 图中节点①,/art/runtime/thread.cc 中的函数Thread:CreateNativeThread部分节选代码如下:

图 3-9 Thread:CreateNativeThread 节选

可知:

  • JNIENV 创建不成功时产生 OOM 的错误信息为 “Could not allocate JNI Env”,与文中 OOM 一一致

pthread_create失败时抛出 OOM 的错误信息为"pthread_create (%s stack) failed: %s".其中详细的错误信息由 pthread_create 的返回值(错误码)给出。错误码与错误描述的对应关系可以参见 bionic/libc/include/sys/_errdefs.h中的定义。文中 OOM 二的具体错误信息为"Out of memory",就说明 pthread_create 的返回值为 12。

图 3-10 系统错误定义 _errdefs.h

2. 图中节点②和③是创建 JNIENV 过程的关键节点,节点②/art/runtime/mem_map.cc 中 函数 MemMap:MapAnonymous 的作用是为 JNIENV 结构体中Indirect_Reference_table(C 层用于存储 JNI 局部 / 全局变量)申请内存,申请内存的方法是节点③所示的函数ashmem_create_region(创建一块 ashmen 匿名共享内存, 并返回一个文件描述符)。节点②代码节选如下:

图 3-11 MemMap:MapAnonymous 节选

我们线上的OOM 一的错误信息"ashmem_create_region failed for ‘indirect ref table’: Too many open files",与此处打印的信息吻合。"Too many open files"的错误描述说明此处的 errno(系统全局错误标识)为 24(见图 [3-10] 系统错误定义 _errdefs.h)。由此看出我们线上的 OOM 一是由于文件描述符数目已满,ashmem_create_region 无法返回新的 FD 而导致的

3. 图中节点④和⑤是调用 C 库创建线程时的环节,创建线程首先 调用 __allocate_thread 函数申请线程私有的栈内存 (stack) 等,然后 调用 clone 方法进行线程创建.申请 stack 采用的时 mmap 的方式,节点⑤代码节选如下:

图 3-12 __create_thread_mapped_space 节选

打印的错误信息与图 [3-7] 中进程逻辑地址占满导致的 OOM 错误信息吻合,图 [3-7] 中错误信息" Try again"说明系统全局错误标识 errno 为 11(见图 [3-10] 系统错误定义_errdefs.h). pthread_create 过程中,节点4相关代码如下:

图 3-13 pthread_create 节选

此处输出的错误日志"pthread_create failed: clone failed: %s"与我们线上发现的 OOM 二吻合,图 [3-6] 中的错误描述" Out of memory"说明系统全局错误标识 errno 为 12(见图 [3-10] 系统错误定义 _errdefs.h)。 由此线上的 OOM 二就是由于线程数的限制而在节点 5 clone 失败导致 OOM。

四、结论及监控

4.1导致OOM发生的原因

综上,可以导致 OOM 的原因有以下几种:

1. 文件描述符 (fd) 数目超限,即 proc/pid/fd 下文件数目突破 /proc/pid/limits 中的限制。可能的发生场景有:短时间内大量请求导致 socket 的 fd 数激增,大量(重复)打开文件等 ;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

1710921739523)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-Q5vuszMX-1710921739523)]

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值