APP性能監(jiān)控之FOOM檢測實(shí)踐

原文:橘子不酸丶
轉(zhuǎn)載:https://juejin.im/post/5e154ad35188253aad5c7c37

前言

iOS開發(fā)中 隨著應(yīng)用在版本迭代過程中,APP大小逐漸增大,APP占用內(nèi)存也隨著功能的增加而越來越多,因此APP在線上發(fā)生FOOM(Foreground Out Of Memory)的頻率也會(huì)越來越高。

一般對于用戶而言,發(fā)生FOOM時(shí)和crash表現(xiàn)一樣,然而在通常在線上crash收集過程中FOOM卻并不能被收集到,所以如何收集應(yīng)用在線上FOOM發(fā)生的頻率,以及FOOM發(fā)生時(shí)內(nèi)存占用情況也是我們的一個(gè)重要的監(jiān)控指標(biāo)。

原理

Facebook早在2015年8月提出FOOM檢測辦法,大致原理是排除各種情況后,剩余的情況是FOOM,Reducing FOOMs in the Facebook iOS app。
本文則基于這種檢測辦法來具體分析和實(shí)現(xiàn)一次OOM發(fā)生后的區(qū)分以及上報(bào)。

image

怎么判斷一次OOM

  1. APP版本升級
  2. 調(diào)用Exit()或者Abort()退出應(yīng)用
  3. APP發(fā)生Crash
  4. 用戶通過Home鍵強(qiáng)制退出APP
  5. 手機(jī)系統(tǒng)升級引起的重啟
  6. APP在后臺(tái)BOOM!
  7. APP在前臺(tái)FOOM!

實(shí)現(xiàn)

1.啟動(dòng)檢測

當(dāng)APP啟動(dòng)時(shí),我們需要盡可能早的在application:didFinishLaunchingWithOptions:之后開啟我們的OOM檢測。

typedef void (^MJYPOutOfMemoryEventHandler)(BOOL wasInForeground);
- (void)beginMonitoringMemoryEventsWithHandler:(nonnull MJYPOutOfMemoryEventHandler)handler;

2.APPVersion以及OSVersion記錄

獲取當(dāng)前APP版本號并取出上次存儲(chǔ)的APP版本號進(jìn)行對比

  NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
  NSString *majorVersion = infoDictionary[@"CFBundleShortVersionString"];
  NSString *minorVersion = infoDictionary[@"CFBundleVersion"];
  return [NSString stringWithFormat:@"%@.%@", majorVersion, minorVersion];

獲取當(dāng)前OSVersion并取出上次存儲(chǔ)的OSVersion進(jìn)行對比

  [NSString stringWithFormat:@"%@.%@.%@", @(version.majorVersion), @(version.minorVersion), @(version.patchVersion)]

3.Terminate以及前后臺(tái)切換監(jiān)聽

監(jiān)聽terminate和前后臺(tái)切換之后,我們需要處理用戶強(qiáng)制退出程序也就是terminate的情況,以及還需要來記錄和區(qū)分前后臺(tái)的狀態(tài),當(dāng)發(fā)生OOM時(shí)我們則可以通過此狀態(tài)來區(qū)分FOOM。

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:UIApplicationWillTerminateNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];

4.Hook exit()以及abort()方法監(jiān)聽程序退出

abort() : 立即結(jié)束,不做任何操作。

exit() : 釋放所有靜態(tài)全局的對象、緩存,關(guān)掉所有的I/O通道,然后終止程序。如果有函數(shù)通過atexit來注冊,還會(huì)調(diào)用注冊的函數(shù),如果atexit函數(shù)拋出異常就會(huì)直接調(diào)用terminate。

atexit() : 注冊程序正常終止時(shí)要被調(diào)用的函數(shù),一個(gè)進(jìn)程可以登記多達(dá)32個(gè)函數(shù),這些函數(shù)將由exit自動(dòng)調(diào)用,通常這32個(gè)函數(shù)被稱為終止處理程序,并調(diào)用atexit函數(shù)來登記這些函數(shù)。

我們可以通過fishhook來hook C中的這兩個(gè)退出程序的函數(shù)。

fishhook是Facebook提供的一個(gè)動(dòng)態(tài)修改鏈接mach-O文件的工具。利用MachO文件加載原理,通過修改懶加載表(Lazy Symbol Pointers)和非懶加載表(Non-Lazy Symbol Pointers)這兩個(gè)表的指針達(dá)到C函數(shù)HOOK的目的。fishhook的具體原理可以參考Github源碼以及簡介。

具體做法如下:

static void (*orig_exit)(int);
static void (*_orig_exit)(int);
static void (*orig_abort)(void);
void my_exit(int value) {
    [[MJYPOutOfMemoryMonitor sharedInstance] appDidExit];
    orig_exit(value);
}
void _my_exit(int value) {
    [[MJYPOutOfMemoryMonitor sharedInstance] appDidExit];
    _orig_exit(value);
}
void my_abort(void) {
    [[MJYPOutOfMemoryMonitor sharedInstance] appDidExit];
    orig_abort();
}
+ (void)load {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
      rebind_symbols((struct rebinding[1]){
          {"_exit", (void *)_my_exit, (void *)&_orig_exit}
      }, 1);
      rebind_symbols((struct rebinding[1]){
          {"exit", (void *)my_exit, (void **)&orig_exit}
      }, 1);
      rebind_symbols((struct rebinding[1]){
          {"abort", (void *)my_abort, (void **)&orig_abort}
      }, 1);
  });
}

5.用戶Crash過濾

一般情況下,我們可以在收集到用戶Crash發(fā)生時(shí),來處理過濾Crash的情景。
比如說我們使用Tencent的Bugly庫時(shí),我們可以在Bugly回調(diào)attachmentForException:時(shí)進(jìn)行調(diào)用過濾。
[[MJYPOutOfMemoryMonitor sharedInstance] appDidCrash];//OOM監(jiān)控調(diào)用

6.記錄OOM上報(bào)

當(dāng)APP啟動(dòng)時(shí),對上述情況進(jìn)行判斷,排除以上情況之后,我們則認(rèn)為APP在上一次運(yùn)行過程中發(fā)生了FOOM。此時(shí)則上報(bào)上一次發(fā)生FOOM時(shí)的頁面信息場景以及內(nèi)存對象分配。

其他誤判情況

前臺(tái)卡死引起系統(tǒng)watchdog強(qiáng)殺,通常原因是前臺(tái)線程過多或者發(fā)生死鎖,或CPU使用率持續(xù)過高等,這類強(qiáng)殺無法被App捕獲,需要結(jié)合卡頓監(jiān)控來加以區(qū)分。

后續(xù)

單獨(dú)的知道我們上一次APP運(yùn)行過程中發(fā)生FOOM后,我們可以知道我們應(yīng)用的線上運(yùn)行內(nèi)存穩(wěn)定性,后續(xù)則需要完善在FOOM場景發(fā)生時(shí)的內(nèi)存堆棧數(shù)據(jù)和內(nèi)存對象分配,以及頁面信息記錄。附FOOM Report Demo

參考資料

iOS微信內(nèi)存監(jiān)控 - 楊津

OOMDetector組件 - 手Q團(tuán)隊(duì)

FBRetainCycleDetector
FBMemoryProfiler
FBAllocationTracker

Reducing FOOMs in the Facebook iOS app

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 歡迎大家前往云+社區(qū),獲取更多騰訊海量技術(shù)實(shí)踐干貨哦~ 作者:楊津,騰訊移動(dòng)客戶端開發(fā) 高級工程師 由WeTest...
    yuguang1閱讀 500評論 0 0
  • iOS微信內(nèi)存監(jiān)控 云加社區(qū) 2018-03-02 10:45:31 更多騰訊海量技術(shù)文章,請關(guān)注云+社區(qū):htt...
    樹懶啊樹懶閱讀 786評論 1 5
  • 以下為文章正文,如果覺得有用,歡迎給她打賞。 為了能夠第一時(shí)間發(fā)現(xiàn)程序問題,應(yīng)用程序需要實(shí)現(xiàn)自己的崩潰日志收集服務(wù)...
    赤色追風(fēng)閱讀 2,566評論 1 11
  • 相信最近因?yàn)橐咔?,很多人足不出戶,特別像我這種身處湖北境內(nèi)的,人人自危。 今天一覺醒來,又有了新的壞消息??票葔嫏C(jī)...
    珩玥閱讀 1,297評論 11 26
  • 01 近段時(shí)間的早上,在我家,都會(huì)上演一段可歌可泣的“小別離”。 娃扯著嗓子,抱著她爸大腿,可憐兮兮地哀求,“爸爸...
    毅行生長閱讀 585評論 0 5