文档章节

【腾讯开源】iOS爆内存问题解决方案-OOMDetector组件

腾讯开源
 腾讯开源
发布于 01/13 10:52
字数 2925
阅读 272
收藏 2
点赞 0
评论 0

组件介绍

OOMDetector是手Q自研的IOS内存监控组件,腾讯内部目前已有多个App接入了OOMDetector,它主要有以下两个功能:

  • 爆内存堆栈统计:负责记录进程内存分配堆栈和内存块大小,在爆内存时Dump堆栈数据到磁盘

  • 内存泄漏检测:检测内存泄漏,目前支持Malloc内存块和OC对象的泄漏检测

OOMDetector可以快速帮助开发者发现和定位App爆内存问题和内存泄漏,组件目前已经在Github开源,源码地址:https://github.com/Tencent/OOMDetector。

背景

目前业内已有一些比较的IOS内存分析工具,下面逐个介绍这些工具的功能以及它们在使用上的不足。

Allocation

作为IOS开发,我们都很熟悉苹果官方提供的Allocation内存分析工具,在开发调试阶段,可以用Allocation详细分析App各模块内存占用。Allocation对App的内存监控比较全面,能监控到所有堆内存以及部分VM内存分配。虽然Allocation的功能比较强大,但是它也有比较明显的使用局限性,主要表现为以下两点:

  • 无法独立在App运行,只能在调试阶段连接Mac使用

  • 性能较差,大型App开启后容易引发卡死

这两点限制决定了Allocation只适合于在开发阶段辅助分析代码中存在的内存问题,而无法直接对线上用户的问题进行监控和定位。

FBAllocationTracker

FBAllocationTracker是Facebook开源的内存分析工具,它的原理是用 Method Swizzling替换原本的alloc方法,这样可以在App运行时记录所有OC实例的分配信息,帮助App在运行阶段发现一些OC对象的异常增长问题。相比Allocation,FBAllocationTracker对App性能影响较低,可以在App中独立运行。但是这个工具也有比较明显的缺陷:

  • 监控范围不够全面,只能监控OC对象,不能监控C++对象和malloc内存块以及VM内存

  • 没有内存对象分配的堆栈信息,对于开发者来说很难只通过对象的类型和数量定位到内存增长的原因

综上所述,FBAllocationTracker虽然能独立在App中运行,但是监控的内存范围太小,同时记录的对象信息也过于简单,对于分析内存问题帮助十分有限。

内存问题一直是手Q的关注重点,为了保证线上大盘用户的内存质量,我们希望有一款工具能够帮助监控和定位线上用户的内存问题。基于这样的背景,我们团队自研了OOMDetector组件。OOMDetector通过Hook系统底层的内存分配方法,能够记录到进程所有内存分配的堆栈信息,同时组件能够在对性能流畅度影响不大的情况下能够保证在App中独立运行,可以方便用于分析和监控线上用户的内存问题(爆内存或者内存泄漏问题)。

组件原理

爆内存堆栈统计

爆内存堆栈监控原理

爆内存堆栈监控的实现原理如图1所示,通过Hook IOS系统底层内存分配的相关方法(包括malloc_zone相关的堆内存分配以及vm_allocate对应的VM内存分配方法),跟踪并记录进程中每个对象内存的分配信息,包括分配堆栈、累计分配次数、累计分配内存等,这些信息也会被缓存到进程内存中。在内存触顶的时候,组件会定时Dump这些堆栈信息到本地磁盘,这样如果程序爆内存了,就可以将爆内存前Dump的堆栈数据上报到后台服务器进行分析。

图1 爆内存监控原理

性能挑战

App的内存分配方法的调用频率非常高,在大型App中可能高达10W/次每秒。要Hook这类方法对组件的性能来说是极大的挑战,因为如果组件本身耗时的话就很容易导致App卡顿甚至卡死。在OOMDetector中,我们对Hook方法代码的执行效率进行了严格控制,也采取了一些策略对Hook方法中耗时较多的堆栈回溯和锁等待进行了优化:

  • 优化堆栈回溯方法

对于堆栈回溯,系统提供了backtrace_symbols方法可以直接获取堆栈信息,但是这个方法特别耗时。所以我们根据堆栈的回溯原理实现了更高效的堆栈回溯方法,优化后的方法在运行时只会获取堆栈函数的地址信息,在回写磁盘的时候再根据动态库的地址范围拼装成如图2所示堆栈格式(类似Crash堆栈),后台服务器利用atos命令和符号表文件就可以还原出对应的堆栈内容。通过这种方式可以把耗时较高的符号还原工作放到服务器端,客户端只需要执行耗时较少的堆栈函数地址回溯操作,优化后的堆栈回溯方法耗时低于1us。

图2 堆栈格式

  • 优化锁等待耗时

对于多线程的内存分配,为了保证线程安全,堆栈数据的插入操作必须要上锁。对于这种高频调用的方法,锁的性能是我们最关心的指标。IOS开发中NSLock和@synchronized是比较常用的,那么这两种锁的性能如何呢?

我们通过测试代码对IOS中常用的锁进行了测试,总结了图2所示的各种锁的性能比较图,根据图3的测试结果,NSLock和@synchronized的性能要低于pthread_mutex,性能最好的是自旋锁OSSpinLock。

自旋锁的原理是,如果自旋锁已经被别的执行单元保持,调用者就一直循环等待锁的释放。相比互斥锁而言,自旋锁不会引起调用者休眠,节省了线程休眠的状态切换,所以有更高的效率,但代价是增加了cpu的使用率。对于我们的场景,因为需要上锁部分的代码执行耗时较少,采用OSSpinLock的自旋锁并不会显著增加cpu的使用率,所以我们优先考虑锁的效率采用了OSSpinLock的方案。

图3 各种锁的性能比较

堆栈聚类和压缩

之前提到,我们的Hook方法会缓存每个内存分配的堆栈数据。假设App的内存块个数为25W,堆栈平均深度20行,每个堆栈地址采用8字节的整型数据存储,那么25W个堆栈数据将占用40M的内存空间。显然这样的内存增长对于任何App都是不可承受的,所以我们需要对组件的内存占用进行优化。

我们分析爆内存问题时候,只需要分析那些内存占用较大的堆栈,基本不用关心那些内存占用较小的堆栈。所以我们的优化思路也很明确:只保留内存占用较大的堆栈。要完成这个工作就必须对内存中所有堆栈先进行聚类合并,统计出每个堆栈累计的内存值。

具体的优化策略如图4所示,对于每个记录到的分配堆栈,首先通过md5算法将堆栈数据压缩为16字节的md5,通过md5值进行聚类,缓存中只保留16字节的md5数据,只有当某个堆栈的累计内存超过一定阀值时,才会保留原始堆栈信息,这样因为超过阀值的堆栈数量有限,堆栈原始信息占用的空间几乎就可以忽略不计了。

图4 堆栈聚类和压缩原理

采用两种方式可以将堆栈降低到优化前的1/40左右,优化后的组件内存基本不会对App的内存造成太大影响。

数据Dump方案

前面提到,在内存触顶后要将内存中的堆栈数据定时Dump到磁盘中,常规的方案是IO接口直接把数据写入到磁盘。因为数据Dump的频率较高,频繁的IO操作会导致程序卡顿。因为数据Dump的操作是非常高频的,所以我们采用了效率更高的mmap方式。

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间。实现这样的直接映射关系后,写文件的过程进程不会有额外的文件的数据拷贝操作,避免了内核空间和用户空间的频繁切换,如图5所示。根据我们的代码实测,向mmap映射空间写数据的性能与直接写内存一致,效率远高于IO操作。

图5 内存映射原理

那么mmap的回写时机是怎样的?根据官方文档描述,主要有如下时机:

  • 系统内存不足时

  • 进程crash时

  • 主动调用 msync时

mmap 在内存不足时会主动进行回写操作,这样的机制也保证我们的监控组件能在程序爆内存前将缓存中的数据回写到磁盘,从这一点看采用mmap的方式相比常规IO操作也有更强可靠性。

内存泄漏检测

除了爆内存堆栈监控,OOMDetector还集成了内存泄漏检测功能,能够检测Malloc内存块和OC对象的“无主内存泄漏”。所谓“无主内存泄漏”是指内存块在进程内已经没有引用却无法正常释放的内存块。

按照之前介绍的方案,OOMDetector可以记录到每一个对象的分配堆栈信息,要从这些对象中找出 “泄漏对象”,我们需要知道在程序可访问的进程内存空间中,是否有“指针变量”指向对应的内存块,那些在整个进程内存空间都没有指针指向的内存块,就是我们要找的泄漏内存块。如图2所示,在IOS系统中,可能包含指针变量的内存区域有堆内存、栈内存、全局数据区和寄存器,OOMDetector 通过对这些区域遍历扫描即可找到所有可能的“指针变量”,整个扫描流程结束后都没有“指针变量”指向的内存块即是泄漏内存块。

为了避免内存访问冲突,扫描过程需要挂起所有线程,整个过程会卡住程序1-2秒。因为扫描过程较为耗时,这个功能目前主要用于App的测试阶段,与自动化测试结合可快速高效的发现泄漏问题。

图6 内存泄漏检测原理

展望

开源只是开始,我们后续仍会不断对OOMDetector组件进行改进,也欢迎大家对组件多提意见。如果你的IOS应用也在受到内存问题困扰或者你也对IOS内存监控技术感兴趣,那么来了解下我们的组件吧!

© 著作权归作者所有

共有 人打赏支持
腾讯开源
粉丝 56
博文 22
码字总数 51657
作品 0
iOS动画效果合集、飞吧企鹅游戏、换肤方案、画板、文字效果等源码

iOS精选源码 动画知识运用及常见动画效果收集(http://www.code4app.com/thread-13354-1-1.html) 3D卡片拖拽卡片叠加卡片(http://www.code4app.com/thread-30475-1-1.html) iFIERO - FLYING P......

sunnyaigd
07/18
0
0
iOS天气动画、高仿QQ菜单、放京东APP、高仿微信、推送消息等源码

iOS精选源码 TYCyclePagerView iOS上的一个无限循环轮播图组件(http://www.code4app.com/thread-14507-1-1.html) iOS高仿微信完整项目源码(http://www.code4app.com/thread-14695-1-1.html)......

sunnyaigd
06/12
0
0
面试官自述:面向高级开发人员的iOS面试问题

当您准备进行技术性iOS面试时,了解您可能会询问哪些主题以及经验丰富的iOS开发人员期望什么是非常重要的。 这是许多硅谷公司用来衡量iOS候选人资历水平的一系列问题。 这些问题涉及iOS开发的...

菇哒微课
04/26
0
0
(转)直接拿来用!最火的iOS开源项目(二)

“每一次的改变总意味着新的开始。”这句话用在iOS上可谓是再合适不过的了。GitHub上的iOS开源项目数不胜数,iOS每一次的改变,总会引发iOS开源项目的演变,从iOS 1.x到如今的iOS 7,有的项目...

孙启超
2013/06/21
0
1
开发微信H5视频秀项目遇到的坑

介绍 手头上正好有个项目,需要做一个微信端H5视频秀的一个项目,想想好像挺简单的,由两个视频组成,播放完第一个视频后点击按钮继而播放第二个视频。好了,结果微信的坑TM的多 问题排查 自...

🚲Allen
05/18
0
0
谷歌Flutter跨平台应用开发SDK迎来首个发行预览版本

谷歌Flutter跨平台应用开发SDK迎来首个发行预览版本 2018-06-22 12:26编辑: 枣泥布丁分类:程序人生来源:程序师 跨平台Google Flutter预览版本 招聘信息: 图像处理及模式识别工程师 C/C+...

枣泥布丁
06/22
0
0
React Native VS Flutter评测

React Native VS Flutter评测 编辑于 11:34

纪洪波
06/27
0
0
100%移植阿里云移动测试技术_竟仅需1周?!——移动测试专有云(3)——内容详解

一、自动化测试服务 Android兼容性测试 Android兼容性测试旨在帮助解决Android应用在不同真机机型上的各类兼容性问题,包括 Crash/ANR分析、6项性能分析、UI检测、3个版本的覆盖安装检测等。...

乐乎无趣
2017/11/02
0
0
Java转iOS-第一个项目总结(2)

遇到问题和解决方案     本文是Java转iOS-第一个项目总结(1)的内容补充,分析遇到的一些问题和解决方案,分享一些收获。 1.UITableView滑动卡顿的优化    因为 的cell中有很多图片,在4...

蛙牛
2015/04/20
0
11
移动web页面支持弹性滚动的3个方案

position:fixed 和 overflow:auto 进行简单的布局实现我们需要的效果,而在手机端遇到的问题如下:

同一种调调
2014/07/17
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

eclipse SVN 项目重新定位

SVN 重新定位 1.方法一 首先:在Eclipse中选择Windows-> Show View->others 就会出现【SVN资源库/SVN Repositories】,选中后,点击确认; 然后:选中原有的地址,选择【重新定位/Relocate】...

qimh
7分钟前
0
0
Linux 第29课 ——Linux集群架构(下)

Linux集群架构(下) 八、DR模式搭建 8.1 准备工作 试验需求三台机器: 分发器,也叫调度器(简写为dir) 192.168.112.136 ying01 rs1 192.168.112.138 ying02 rs2 192.168.112.139 ying03 vip...

feng-01
12分钟前
0
0
轻松搭建svn版本管理工具+svnmanager管理客户端

前面的文章有写过svn版本管理工具的安装是基于svn的安装包进行安装,对于svn与apache的结合还得下svn和apache的模块进行结合过程比较繁琐,今天来介绍下通过centos的yum来安装svn能够快速安装...

javazyw
21分钟前
0
0
keepalived配置高可用集群

Linux集群概述 根据功能划分为两大类:高可用和负载均衡 高可用集群通常为两台服务器,一台工作,另外一台作为冗余,当提供服务的机器宕机,冗余将接替继续提供服务 实现高可用的开源软件有:...

TaoXu
27分钟前
0
0
mysql联表批处理操作

1 概述 mysql中的单表增删改查操作,可以说是基本中的基本. 实际工作中,常常会遇到一些基本用法难以处理的数据操作,譬如遇到主从表甚至多级关联表的情况(如一些历史问题数据的批量处理),考虑到...

社哥
29分钟前
0
0
IntelliJ IDEA 详细图解最常用的配置,适合刚刚用的新人。

刚刚使用IntelliJ IDEA 编辑器的时候,会有很多设置,会方便以后的开发,磨刀不误砍柴工。 比如:设置文件字体大小,代码自动完成提示,版本管理,本地代码历史,自动导入包,修改注释,修改...

kim_o
44分钟前
0
0
Google Java编程风格指南

目录 前言 源文件基础 源文件结构 格式 命名约定 编程实践 Javadoc 后记 前言 这份文档是Google Java编程风格规范的完整定义。当且仅当一个Java源文件符合此文档中的规则, 我们才认为它符合...

niithub
46分钟前
0
0
java.net.MalformedURLException异常说明

1.异常片段 Java代码中,在进行URL url = new URL(urllink)操作时,提示以下异常信息,该类异常主要问题出在参数urllink上面。 异常片段1 java.net.MalformedURLException at java.ne...

lqlm
47分钟前
1
0
CentOS7修改mysql5.6字符集

解决办法:CentOS7下修改MySQL数据库字符编码为UTF-8,UTF-8包含全世界所有国家所需要的字符集,是国际编码。 具体操作如下: 1.进入MySQL [root@tianqi-01 ~]# mysql -uroot -p Enter passw...

河图再现
48分钟前
0
0
DevExpress v18.1新版亮点——WPF篇(一)

用户界面套包DevExpress v18.1日前终于正式发布,本站将以连载的形式为大家介绍各版本新增内容。本文将介绍了DevExpress WPF v18.1 的新功能,快来下载试用新版本!点击下载>> Accordion Co...

Miss_Hello_World
51分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部