如何斷定發生了OOM

OOM (out of memory) ,根據Facebook的這篇文章,分爲FOOM( foreground OOM )和BOOM ( background OOM )。從用戶角度來看,FOOM與通常crash無異。可是因OOM致使的crash沒法被app捕獲,這就爲咱們斷定OOM帶來了難度。ios

didReceiveMemoryWarning

要判斷是否發生了OOM,首先想到的就是didReceiveMemoryWarning。git

咱們知道,當內存不足時系統會給app發送didReceiveMemoryWarning的警告。可是具體是app用了多少內存之後會發送這個警告呢?發送了警告以後是否是app就crash了呢?OOM致使crash前,app是否必定會收到這個警告呢?github

內存上限

根據官方文檔的說明,若是系統可用內存太低,又不能經過殺掉掛起的app來釋放內存的時候,UIKit就會給正在運行的app發送一個low-memory警告。這裏並無提到內存上限的大小。bash

實際上,根據Jetsam機制,OOM發生時,在系統強殺App前,會判斷優線程先級,按照優先級去釋放優先級低還使用內存多的線程。這個優先級規定是:內核用線程的優先級是最高的,操做系統的優先級其次,App 的優先級排在最後。而且,前臺 App 程序的優先級是高於後臺運行 App 的。 因此若是OOM發生的時候,你的app若是在後臺也有可能被強殺。若是你的app在前臺,能使用內存上限也不是一個固定值。微信

收到低內存警告必定會crash嗎

實際上,若是收到內存警告後,內存並無快速持續上漲,咱們又很是快速的釋放了內存,app並不會crash。也就是說只要及時地下降內存的使用,app就不會crash。app

OOM致使crash前,app必定會收到低內存警告嗎

咱們能夠實際測試一下:async

在主線程執行下面的代碼:測試

while (true) {
        NSData *bigData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"some_image" withExtension:@"png"]];
        [oom addObject:[bigData copy]];
    }
複製代碼

bigData足夠大的狀況下,好比幾十k,app很快crash,而且沒有收到低內存警告。由於這時,內存增加很快,主線程又忙。ui

那若是在其餘線程執行上述代碼呢?spa

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (true) {
            NSData *bigData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"some_image" withExtension:@"png"]];
            [oom addObject:[bigData copy]];
        }
    });
複製代碼

這時,app收到低內存警告了。也就是說,雖然都是急速的上漲內存,若是主線程不那麼忙,就能收到低內存警告。

因此,發生OOM時,並不必定能收到低內存警告。Facebook的這篇文章也說並非總能收到內存警告:

If the rate of memory consumption increases drastically, the application can be killed without receiving any signal that memory is running out. On iOS, the OS does its best to send a memory warning to the app, but there is no guarantee that one will always be received before the OS evicts the process from memory.

我想咱們證實了這個觀點。

總結

經過上面的論述,咱們知道:收到低內存警告不必定會crash,OOM時也不必定能收到低內存警告。

排除法

Facebook的Reducing FOOMs in the Facebook iOS app提到,他們是經過在app啓動時,排除啓動緣由來找到OOM的:

在app啓動時,咱們依次判斷:

  1. App沒有升級
  2. App沒有調用exit()或abort()退出
  3. App沒有出現crash
  4. 用戶沒有強退App
  5. 系統沒有升級/重啓
  6. App當時沒有後臺運行
  7. App出現FOOM

這個項目是上述流程的實現。

可是這個模型並不完美。其中最大的漏洞就是那些不能被捕獲的crash。

不能被app捕獲的crash

不能被app捕獲的crash通常都是由於系統強殺引發的,OOM是其中一種,但還有其餘狀況。

其餘被系統強殺的緣由最多見有兩種,一是後臺任務超時和二是主線程長時間卡頓被watchdog強殺,也就是常見的0x8badf00d。固然,除了這兩種狀況還有其餘被系統強殺的緣由,好比電池過熱,啓動時間過長等。

隨着iOS系統的升級,系統強殺的種類和閾值可能都會發生變化。

因此,這種排除法有必定誤報的可能。

結論

排除法是facebook採用的方法,微信的Matrix應該也採用了這個方法。可是,排除法會有必定誤報的可能,實現難度也更高。

從收到內存警告到app crash,中間有幾秒的時間,一種說法是6秒,但我實測是4秒左右,系統的日誌也是在這段時間產生的。若是app收到了低內存警告,又在幾秒鐘以內crash了,基本上就能夠100%肯定發生了OOM。

對於收不到低內存警告的OOM,咱們就無能爲力了。但我認爲這種狀況是少見的極端狀況。

因此兩種斷定OOM的方法都不完美,綜合實現難度來看,利用內存警告判定是否發生了OOM,也許是一個比較不錯的選擇。

參考資料:

  1. Reducing FOOMs in the Facebook iOS app
  2. Matrix-iOS 內存監控
  3. 14 | 臨近 OOM,如何獲取詳細內存分配信息,分析內存問題?
相關文章
相關標籤/搜索