0%

libc崩溃治理

开发多年以来,系统libc.so abort 是每个App都会遇到的问题,早期一直没有研究明白到底为什么导致的崩溃,一直猜测是系统bug,内存泄漏导致内存溢出。之前通过解决native层的内存泄漏可以减少少量的crash。libc.so abort此项崩溃占一直占比30%+,最近看到一些大神的分析,就这个问题的解决和实践做个总结。

1 jemalloc的浅析

内存分配器——jemalloc

jemalloc是比较著名的内存分配器,在redis、android等系统中广泛使用,Android早在5.0就切换到了jemalloc,相比于其他传统的内存分配器例如ptmalloc等,jemalloc的优势是多线程下内存分配高性能而闻名,jemalloc有不同 arena内存结构来避免线程同步、降低锁的粒度、使用原子语义、线程tsd缓存内存来提升分配性能等等;对于内存碎片的减少,有经过设计的多种 size_class、伙伴算法、gc等。

jemalloc 4.5

jemalloc在4.5.0以及这个版本之前,脏页(脏页是指内存从tsd缓存flush回到arena和chunk的内存page)的释放条件是根据活动页页数(正在使用的内存页)和脏页页数比例来决定的,当比例小于某个值时jemalloc才会开启GC,归还系统,不然jemalloc会一直持有内存块,不会归还给操作系统,这样带来的一个问题是:如果某些场景脏页和活动页的比例一直达不到GC的标准,内存一直被jemalloc持有,虚拟内存持续被消耗,而得不到释放,就会oom,我想手淘这么复杂的场景,这种情况绝对存在,所以手淘之前的oom一部分跟这个机制也有关系。

5.x jemalloc带来的优化和问题

4.5.0之后的版本即5.x版本的jemalloc更改了脏页释放条件。脏页的释放不由脏页的数量决定,而由用户申请、释放内存的频率决定,jemalloc为每一个内存区域arena设置一个tick数统计,tick默认1000,tick值减到小于0时,就会开启对脏页的GC,归还内存给操作系统。虽然貌似改进了之前GC的问题,但如果一个应用线程在启动后申请了大量的内存释放,之后再也不进行任何内存申请、释放操作,此时的tick计数还没达到1000次,之前申请后释放的内存将会一直标记为脏页,直到线程退出后才会释放。

5.1.0版本jemalloc的bug

问题来了,5.1.0版本的jemalloc有内存泄漏问题。具体bug可见官方github issue:https://github.com/jemalloc/jemalloc/issues/1497 。 具体的问题是:当一个线程还没有申请内存之前,这个线程先对其它线程申请的内存做释放操作,那么接下来这个线程所有申请与释放的内存都会标记为脏页,这些脏页不再释放也就是被GC归还给操作系统,造成严重的内存泄漏。

后续

遗憾的是,到目前为止android 11.0.0_r3仍采用的是5.1.0版本jemalloc,这也就预示在未来一年甚至更长时间内,这个问题会一直存在,这或许是google一种变向的推动android arm64架构升级的办法。

解决

目前已经通过发布64位包,提升虚拟内存大小,极大缓解了因虚拟内存不足导致的oom crash问题,其他外部公司我了解到的,例如微信等APP也通过64位包来解决此问题。
64包位上线后native crash更是下降了近80%。