百度APP iOS端内存优化实践-大块内存监控方案

发表于 2年以前  | 总阅读数:2023 次

一. 背景

‍内存不足引发的APP崩溃通常称为OOM(Out Of Memory),iOS端无法捕获OOM异常,也得不到任何堆栈信息,给我们排查和解决问题带来很多困扰。引起OOM的原因归根结底就是因为内存分配不合理引起的,尤其是内存处于危险水位时单次内存分配过大引起Jetsam机制开始生效而杀掉进程,通过我们线上数据监控,百度APP客户端单次内存分配超过30M的case很多。

针对这种潜在的引起OOM的隐患,我们开发了一种大内存分配监控方案,充分利用线上监控优势(丰富真实的用户场景和用户路径)和线下流水线优势(可获取更多的堆栈信息),其中线上环境除了功能实现外,还要重点考虑稳定性,不能引入额外的性能问题,经过技术探索我们解决了此类难题,线上监控和线下流水线监控相结合实现对百度APP大块内存的监控。

二. 技术方案综述

大块内存监控大体分为两个功能模块,缺一不可:

  • 获取内存分配详情。判断单次内存分配是否超过阈值,若超过阈值,说明是大内存分配行为;
  • 获取堆栈信息。丰富的堆栈信息可直接帮助开发同学定位到产生大内存分配的具体代码,定位分配不合理的case。

最终可通过优化内存分配不合理的case,达到降低OOM率的目标。

三. 获取内存分配详情

3.1 方案对比

关于获取iOS端每次内存分配信息,有如下解决方案:

  1. 通过 hook 内存分配函数 alloc 方法获取,用 swizzle 方法实现 hook,存在的缺点是监控范围不够全面,只能监控 OC 对象,不能监控 C/C++ 对象。
  2. hook 库libsystem_malloc 内存分配函数 malloc_zone_malloc、malloc_zone_calloc、malloc_zone_valloc、malloc_zone_realloc来获取内存信息。这种方案对于 OC 对象和 C/C++ 对象都可监控,但是因为要 hook 系统 C/C++ 方法而不是 OC 方法,目前的技术条件需要使用 fishhook。

百度APP 采用的技术方案如下图所示,首先通过重置 libsystem_malloc 库中的malloc_logger 函数指针获取内存活动详情(分配和释放两种活动),然后通过Type类型过滤出内存分配的活动, 最后获取内存分配大小,该方案可监控 OC 对象和 C/C++ 对象,对iOS框架系统没有侵入性,没有用 fishhook 库所以没有对 mach-o 文件做任何修改,也没有 hook 任何底层分配内存系统方法,从 APP 性能和质量角度来说是最好的选择。

3.2 libsystem_malloc源码分析

libsystem_malloc.dylib 是iOS系统虚拟内存管理的核心库之一,任何涉及到OC、C/C++ 对象的内存分配都会调用该库的API,由它去调用操作系统Mach内核提供的接口去分配或释放内存。具体来说 libsystem_malloc 提供了 malloc_zone_malloc,malloc_zone_calloc,malloc_zone_valloc,malloc_zone_realloc,malloc_zone_free 五个API来实现内存分配和释放,在 iOS 系统中所有涉及到的内存活动都会调用如上接口,当我们App进程需要创建新的对象时,如调用 [NSObject alloc],或释放对象调用 release 方法时(编译器会添加),请求先会走到 libsystem_malloc.dylib 的上述函数。

Apple 已开源此库,从如下地址可以下载到源码:https://opensource.apple.com/source/libmalloc/

void *
malloc_zone_malloc(malloc_zone_t *zone, size_t size)
{
  MALLOC_TRACE(TRACE_malloc | DBG_FUNC_START, (uintptr_t)zone, size, 0, 0);

  void *ptr;
  if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) {
    internal_check();
  }
  if (size > MALLOC_ABSOLUTE_MAX_SIZE) {
    return NULL;
  }

  ptr = zone->malloc(zone, size);    // if lite zone is passed in then we still call the lite methods

  if (malloc_logger) {
    malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE, (uintptr_t)zone, (uintptr_t)size, 0, (uintptr_t)ptr, 0);
  }

  MALLOC_TRACE(TRACE_malloc | DBG_FUNC_END, (uintptr_t)zone, size, (uintptr_t)ptr, 0);
  return ptr;
}

3.3 关键函数malloc_logger

从源码中我们发现malloc_zone_malloc、malloc_zone_calloc、malloc_zone_valloc、malloc_zone_realloc、malloc_zone_free五个API,在每次调用mach内核函数进行内存分配和释放后都有如下函数调用:


if (malloc_logger) {
  malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE, (uintptr_t)zone, (uintptr_t)size, 0, (uintptr_t)ptr, 0);
}

先判断malloc_logger函数指针是否为空,如果不为空会调用上述函数,将内存活动的详细信息通过该函数传递进来,从源码分析的角度来看这是iOS系统提供的一个日志函数,具体函数定义如下所示:


typedef void(malloc_logger_t)(uint32_t type,
                uintptr_t arg1,
                uintptr_t arg2,
                uintptr_t arg3,
                uintptr_t result,
                uint32_t num_hot_frames_to_skip);
extern malloc_logger_t *malloc_logger;

根据源码我们对malloc_logger函数的入参做如下分析:

参数名称 参数意义
type 类别,不同函数入参都不同
arg1 分配内存后zone地址
arg2 malloc_zone_realloc时为零,其他情况代表分配内存大小
arg3 malloc_zone_realloc时代表内存分配大小,其他情况为0
result 新内存起始地址ptr
num_hot_frames_to_skip 值都为0

对于第一个type字段,不同函数都不同,具体值后面详细解释,此外,我们发现malloc_logger生命为extern类型,在C++中声明extern关键字的全局变量和函数可以使得它们能够跨文件被访问。

3.4 通过重置malloc_logger函数指针获取内存活动详情

在前面章节我们说过malloc_logger为extern全局变量,所以通过以下步骤可以重置该变量获取内存活动详情;

1. 引入libmalloc头文件malloc/malloc.h

2. 定义函数bba_malloc_stack_logger,参数定义与源码定义完全一致,这样做的目的是防止实参传递的时候出现类型和参数个数不一致的问题。

3. 先保存malloc_logger函数指针的值到一个临时变量origin_malloc_logger,目的是保存系统原始调用方法,交换函数指针后还要调用此方法。


#import <malloc/malloc.h>  
typedef void (malloc_logger_t)(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t result, uint32_t num_hot_frames_to_skip); 
//定义函数bba_malloc_stack_logger
void bba_malloc_stack_logger(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t result, uint32_t backtrace_to_skip); 
// 保存malloc_logger到临时变量origin_malloc_logger
orgin_malloc_logger = malloc_logger;

4. 将malloc_logger赋值为自定义函数bba_malloc_stack_logger,在定义函数中先调用原始的系统方法origin_malloc_logger,该方法的调用保证了本方案对系统没有侵入性,接下来做大块内存检测。


//malloc_logger赋值为自定义函数bba_malloc_stack_logger
malloc_logger = (malloc_logger_t *)bba_malloc_stack_logger; 
//bba_malloc_stack_logger具体实现
void bba_malloc_stack_logger(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t result, uint32_t backtrace_to_skip)
{
    if (orgin_malloc_logger != NULL) {
        orgin_malloc_logger(type, arg1, arg2, arg3, result, backtrace_to_skip);
    }
    //大块内存监控
    ......
}

经过上面四个步骤,malloc_zone_malloc、malloc_zone_calloc、malloc_zone_valloc、malloc_zone_realloc每次内存分配结束后,调用如下函数,因为malloc_logger现在不为空,具体值为bba_malloc_stack_logger,所以在bba_malloc_stack_logger中可以获取内存分配活动详情。

if (malloc_logger) {
    malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE, (uintptr_t)zone, (uintptr_t)size, 0, (uintptr_t)ptr, 0);
}

3.5 通过type类型过滤出内存分配详情

通过上面章节我们知道malloc_zone_malloc、malloc_zone_calloc、malloc_zone_valloc、malloc_zone_realloc、malloc_zone_free五个API都会调用bba_malloc_stack_logger,其中的API实现又各有不同,malloc_zone_malloc、malloc_zone_calloc、malloc_zone_valloc代表内存分配,malloc_zone_realloc代表内存先释放再分配,malloc_zone_free代表内存释放,不同的API调用是通过入参type来区分的,所以本技术方案通过type反解析来获取内存分配,过滤掉内存释放。

API 含义 type
malloc_zone_malloc 分配内存给一个对象 MALLOC_LOG_TYPE_ALLOCATE MALLOC_LOG_TYPE_HAS_ZONE
malloc_zone_calloc 分配内存给多个对象 MALLOC_LOG_TYPE_ALLOCATE MALLOC_LOG_TYPE_HAS_ZONE MALLOC_LOG_TYPE_CLEARED
malloc_zone_valloc 分配内存给一个对象 MALLOC_LOG_TYPE_ALLOCATE MALLOC_LOG_TYPE_HAS_ZONE
malloc_zone_realloc 重新分配内存 MALLOC_LOG_TYPE_ALLOCATE MALLOC_LOG_TYPE_DEALLOCATE MALLOC_LOG_TYPE_HAS_ZONE
malloc_zone_free 释放内存 MALLOC_LOG_TYPE_DEALLOCATE MALLOC_LOG_TYPE_HAS_ZONE

3.6 获取单次内存分配大小并判断是否超过阈值

根据源码我们知道malloc_logger函数的入参arg2,arg3代表内存分配大小,不同type代表含义不同,具体见下面表格分析。

参数名称 说明
arg2 type值malloc_zone_realloc时为零,type值为malloc_zone_malloc、malloc_zone_calloc、malloc_zone_valloc代表分配内存大小
arg3 type值malloc_zone_realloc时代表内存分配大小,type值为malloc_zone_malloc、malloc_zone_calloc、malloc_zone_valloc为0

接下来判断是否超过我们设定的阈值的大小,在iOS端根据经验单次内次分配8M就是普遍认为的大块内存,当然这个值由服务端下发可灵活修改,客户端写个默认值即可,但是这个值不建议很小,太小会多次触发大块内存监控逻辑影响我们手机app的性能,超过阈值大小就进入下面的环节获取堆栈信息。

四. 获取堆栈信息

4.1 百度App采用的技术方案

调用系统方法backtrace_symbols可直接获取堆栈信息,但是存在两个问题,第一、方法具有线程属性,必须要在获取堆栈信息的当前线程调用;第二、耗时严重,实测在中高端机(iPhone8以上)有30ms耗时,在低端机(iPhone8以下)有100ms的耗时。如果大块内存是在主线程分配的,上述耗时会引起主线程卡顿问题,故此方案无法针在线上生产环境使用。

针对这个问题,百度App采用的方案如下所示,结合客户端和服务端双端优势,首先利用dyld库生成了APP所有库的起始地址和结束地址,将获取堆栈函数地址信息详情步骤获取完全放在独立子线程中实现,在客户端拼接成crash日志格式,充分利用服务端做堆栈详情反解析操作,客户端只需要在专属子线程执行耗时较少的堆栈函数地址比较操作即可,这样做不会影响所属线程任何操作,更不会引入性能问题,完全克服了系统方法的不足,具体操作流程如下图所示:

4.2 生成所有库的地址范围(dyld)

生成APP中mach-o文件的所有库的信息,该信息包括库名称、库起始地址和结束地址,该操作主要利用dyld库函数在子线程实现,不会占用主线程和内存分配所属线程任何资源。dyld库提供了丰富的api可以获取上述数据,具体来说,_dyld_image_count可获取所有模块数目,dyld_get_image_name 可获取模块名称,dyld_get_image_header可获取每个模块的起始地址,_dyld_get_image_vmaddr_slide获取单个模块的随机基址。

4.3 backtrace获取堆栈地址

当检测到大块内存时,在分配内存所属线程调用backtrace方法获取堆栈函数地址,代码示例:

//返回值depth表示实际上获取的堆栈的深度,stacks用来存储堆栈地址信息,20表示指定堆栈深度。
size_t depth = backtrace((void**)stacks, 20);

那么backtrace耗时究竟如何?通过实践数据证明,堆栈深度设置为20,实测高端机耗时在3ms以内,对性能影响基本可以忽略,此外,不是每次内存分配都需要调用backtrace获取堆栈,只有单次内存分配大小符合大块内存标准才会去获取堆栈。

因此我们在线上生产环境堆栈深度设置为20并且只对高端机开放,深度值太小获取的堆栈信息有限,太大会明显增加backtrace方法耗时,线上数据证明堆栈深度设置为20既能满足性能要求又能解析出合理的堆栈信息,在线下流水线场景下堆栈深度为40以获取更丰富的堆栈信息,两个场景各自发挥自己的优势并相互补充。

4.4 获取每个地址详细信息

经过4.3步骤,我们获取了堆栈地址,但是这个还不够,为了方便服务端直接可以解析出堆栈信息,我们在客户端需要将堆栈地址拼装成下图所示堆栈格式(类似Crash堆栈),libsystem_kernel.dylib 0x1b8a9dcf8 0x1b8a73000 + 175352 ,第一项是库名称,第二项是堆栈函数地址(十六进制),第三项是动态库的起始地址(十六进制),第四项是十进制偏移量,其中第二项堆栈函数地址是通过4.3步骤的backtrace获取的,下面的重点是获取每个地址对应的动态库名称和相对于动态库起始地址的偏移量。

对于上述详细信息的获取,本技术方案将该操作完全放在子线程去实现,因为我们已经在4.2构建好了每个库的起始地址和结束地址,只需要遍历一遍全量库,判断地址是否大于该库起始地址并小于该库的结束地址,那说明该地址就是属于这个库,从而得到该地址的详细信息,堆栈地址和动态库其实地址做差值可获取偏移量信息。

4.5 atos和dsym解析堆栈

经过前面的步骤生成了类似crash日志格式的堆栈,上报服务端后,最后在服务端通过atos命令和dsym文件就可以反解还原出对应的堆栈内容,如:

BaiduBoxApp  0x000000010ff0ceb4 +[BBAJSONSerialization dataFromJSONObject:error:] + 256

通过这种方式可以把耗时较高的符号还原工作放到服务器端,客户端只需要执行耗时较少的堆栈函数地址比较操作即可,放在子线程队列执行,不会影响所属线程任何操作,更不会引入性能问题。

五. 总结

本文主要介绍百度APP大块内存监控方案,目前在生产环境和线下流水线环境均已部署,通过该方案实现了如下三个目标:

  1. 降低OOM率:如果内存分配不合理,优化后对降低OOM率有帮助;
  2. 数据摸底,让我们明确知道百度APP哪些场景有大块内存分配;
  3. 起到预防的作用,因为我们有明确的监控机制,督促每个开发同学创建内存对象时采用适量原则避免无节制分配。

六. 参考链接

[1] libsystem_malloc.dylib源码

https://opensource.apple.com/source/libmalloc/

[2] Mach-O文档介绍

https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/MachOTopics/0-Introduction/introduction.html

[3] Mach-O源码

https://opensource.apple.com/source/xnu/xnu-1228.0.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html

[4]fishhook

https://github.com/facebook/fishhook

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/ke9bMHTfe1Ioq0eVfAxr2A

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
快速配置 Sign In with Apple 5年以前  |  7675次阅读
使用 GPUImage 实现一个简单相机 5年以前  |  5971次阅读
APP适配iOS11 5年以前  |  5749次阅读
 目录