前言
前面的章节,我们已经对内存机制和内存管理有了一定的了解,现在我们将正式进入主题 - 如何设计一款内存检测工具。由于DEBUG环境已有众多的第三方开源工具以及instruments,所以我们的侧重点放在了线上监控。
目标
内存问题造成的后果最常见且最严重的莫过于造成OOM崩溃了,上一章我们对OOM进行了详细的解释,这里引入一个新的概念FOOM:发生在前台的OOM。FOOM由于发生在前台,对于用户的直观感受来说就是发生了Crash,所以我们的工作重心就在于处理FOOM。
监控到线上用户发生了FOOM
当发生FOOM时,期望上报一些内存相关的数据,辅助定位FOOM的原因
SDK功能设计
功能一:用户内存占用统计
APP 占用的内存,一般有resident_size、phys_footprint、virtual_size等统计方式,这里就不一一展开了,一般认为phys_footprint 是最贴近APP 真实内存占用的数据。内存占用数据统计可以开启定时器定时的Log数据,也可以在特定场景下Log数据,比如在ViewDidAppear等。
- (NSInteger)getAllMemoryOccupy {
task_vm_info_data_t vmInfo;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
kern_return_t kernelReturn = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);
if (kernelReturn != KERN_SUCCESS) {
return 0;
}
int64_t memoryUsageInByte = (int64_t) vmInfo.phys_footprint;
return memoryUsageInByte/1024/1024;
}
功能二:监控线上用户发生FOOM
目前采用的是FB的排除法。每次 APP 冷启动时,判断上次 APP 运行过程中记录的特定值,排除掉其他崩溃原因后,得出上次发生了FOOM 的结论。
功能三:上报可疑堆栈
上报可疑堆栈具体是指在程序运行的过程中,不断记录存活内存的相关信息,同时通过backtrace
函数拿到对应的堆栈信息,对这些信息做筛选聚合处理后,写入到本地文件中,当判断发生了FOOM时再上报这些数据。
我们首先看两个 libmalloc 源码中stack_logging_disk.c定义的两个接口
// We set malloc_logger to NULL to disable logging, if we encounter errors
// during file writing
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;
extern malloc_logger_t *__syscall_logger; // use this to set up syscall logging (e.g., vm_allocate, vm_deallocate, mmap, munmap)
当malloc_logger和__syscall_logger函数指针不为空时,malloc/free、vm_allocate/vm_deallocate等内存分配/释放通过这两个指针通知上层,这也是内存调试工具malloc stack的实现原理。有了这两个函数指针,我们很容易记录当前存活对象的内存分配信息(包括分配大小和分配堆栈)。分配堆栈可以用backtrace函数捕获,但捕获到的地址是虚拟内存地址,不能从符号表dsym解析符号。所以还要记录每个image加载时的偏移slide,这样符号表地址=堆栈地址-slide。
关于 malloc_logger和syscall_logger 回调的内存分配,可以简单的理解为,malloc_logger 回调堆内存对象,syscall_logger 回调虚拟内存对象,如下俩图所示:NSArray的创建及CALayer 渲染
有了系统底层的函数回调后,加上backtrace 获取到的堆栈地址,我们就可以根据这些参数设计一套堆栈聚集上报体系。总的来说就是当内存分配的堆栈相同,且同堆栈的内存大小累积值达到设定好的阈值时,写入本地mmap文件,下次冷启动时,判断是否发生了FOOM,确认发生FOOM 时 上报该堆栈。
核心数据流向如下图:
注:__syscall_logger 属于私有api,提审时需移除,线上SDK 只包含堆内存的监控
堆栈上报的功能主要来源于腾讯开源的内存监控SDK Tencent/OOMDetector
此外,附上libmalloc开源文档,
功能四:OC 存活对象监控
通过hook NSObject
的allocWithZone
和dealloc
来统计当前APP 存活的OC 对象,比如统计出所有存活的ViewController 类型对象,这一块数据处理逻辑与上面的可疑堆栈数据上报处理逻辑保持一致。
功能五:内存占用足迹
在非 系统的以及UINavigationController,UITabBarController 的控制器生命周期方法 viewDidLoad 及 viewDidApper 方法记录APP 此刻占用的内存,当然我们的SDK 可以只保留最后一部分数据,完整的数据可以打印在用户日志中。
结语
一个APP 占用过多内存资源的原因是多种多样的,有可能是某些场景存在内存泄漏,有可能是缓存了很多动效文件。如果有办法能够获取到内存异常时所有的内存结点及引用关系那自然是最理想的结果,可惜APP 发生OOM时,官方没有提供接口来监控它,我们只能自行设计合适的内存监控方案,上文的这些功能也只是抛砖引玉,希望大家多多讨论。