基于 JetsamEvent 探究 APP 最大內(nèi)存占用上限

最近在研究 iOS 上內(nèi)存 OOM 的問題,其中看到這篇文章 Handling low memory conditions in iOS and Mavericks 中提到了一點:

Jetsam has another modus operandi, which uses a process memory "high water mark", and will outright kill processes exceeding their HWM.

它這里表明每個進(jìn)程都有一個 HWM,超過這個值時就會觸發(fā) Jetsam 機(jī)制去 kill 這個進(jìn)程。這時我就很好奇了,這個 HWM 的值究竟是多少呢?

1. 在 1GB RAM 的設(shè)備上的內(nèi)存占用上限

于是,我寫了一個 Demo,代碼很簡單:創(chuàng)建了一個定時器,每隔 0.05 秒去不斷的申請分配內(nèi)存并初始化內(nèi)存。

接著,在自己的 iPhone 6P 上運(yùn)行了一下 (運(yùn)行之前把手機(jī)后臺的進(jìn)程全部手動kill了),過一會兒就崩掉了,同時控制臺打印了一行 Log:

Message from debugger: Terminated due to memory issue

這時,我去手機(jī)的 設(shè)置->隱私->分析->分析數(shù)據(jù) 里找到了剛才進(jìn)程被 kill 時生成的 JetsamEvent 日志,在里面找到了一些有用的信息:

"pageSize" : 4096,
{
    "uuid" : "8e20cd83-0b76-3bf3-b909-563a5ea89d89",
    "states" : [
      "frontmost",
      "resume"
    ],
    "killDelta" : 7350,
    "genCount" : 0,
    "age" : 816388409,
    "purgeable" : 0,
    "fds" : 25,
    "coalition" : 1482,
    "rpages" : 166400,
    "reason" : "per-process-limit",
    "pid" : 6281,
    "cpuTime" : 2.1654900000000001,
    "name" : "MemoryLimitTest",
    "lifetimeMax" : 41686
}

首先,pageSize 字段表明當(dāng)前內(nèi)存頁的大小是 4K,大括號里面的內(nèi)容是我們的 Demo 進(jìn)程的一些信息,其中最有用的是這 2 個字段:"reason"、"rpages"。reason 字段表明進(jìn)程被 kill 的原因是超過進(jìn)程的內(nèi)存限制;rpages 字段應(yīng)該是 resident pages 的縮寫,表明進(jìn)程當(dāng)前占用的內(nèi)存頁數(shù)量。

因此,基于上面的 pageSize 與 rpages,可以算出進(jìn)程占用的內(nèi)存大小為:166400 * 4096 / 1024 / 1024 = 650M。也就是說,在 1GB 的設(shè)備上前臺 APP 最大可占用內(nèi)存上限為 650MB。

2. 在 2GB/3GB RAM 的設(shè)備上的內(nèi)存占用上限

然后我又各找了一部 iPhone 8 和 iPhone X,分別運(yùn)行 Demo 后,發(fā)現(xiàn)在這 2 個設(shè)備上的崩潰信息是一樣的:

"pageSize" : 16384
{
    "uuid" : "b8d6682c-5903-3007-b9c2-561d1e6ca9d5",
    "states" : [
      "frontmost",
      "resume"
    ],
    "killDelta" : 18859,
    "genCount" : 0,
    "age" : 1775369503,
    "purgeable" : 0,
    "fds" : 50,
    "coalition" : 691,
    "rpages" : 89600,
    "reason" : "per-process-limit",
    "pid" : 960,
    "cpuTime" : 1.6920809999999999,
    "name" : "MemoryLimitTest",
    "lifetimeMax" : 34182
}

同樣通過 pageSize、rpages 字段的值可以算出:89600 * 16384 / 1024 / 1024 = 1400M。即在 2GB/3GB RAM 的設(shè)備上前臺 APP 內(nèi)存占用上限是 1400M。

這里糾正為:在 2GB RAM 和 iPhone X 上前臺 APP 內(nèi)存占用上限是 1400M

因為根據(jù)我們自己 APP 統(tǒng)計到的值,發(fā)現(xiàn)在 iPhone 8 Plus 上最大內(nèi)存值有超過 1400MB 的,所以又找了一個 iPhone 7 Plus 運(yùn)行了一下(主要是沒找到 8P),發(fā)現(xiàn) JetsamEvent 數(shù)據(jù)跟 iPhone X 果然不一樣:

"pageSize" : 16384
{
    "uuid" : "d6493379-0e74-3a0f-9972-9bbcdd4a7f89",
    "states" : [
      "frontmost",
      "resume"
    ],
    "killDelta" : 19168,
    "genCount" : 0,
    "age" : 2460511710,
    "purgeable" : 0,
    "fds" : 50,
    "coalition" : 1205,
    "rpages" : 131072,
    "reason" : "per-process-limit",
    "pid" : 4476,
    "cpuTime" : 3.0866069999999999,
    "name" : "MemoryLimitTest",
    "lifetimeMax" : 38571
}

通過 pageSize、rpages 字段的值算出:131072 * 16384 / 1024 / 1024 = 2048MB。即在 3GB RAM 的設(shè)備上前臺 APP 內(nèi)存占用上限是 2048MB(iPhone X 除外)。

為什么 iPhone X 不一樣呢,我通過如下代碼獲取了設(shè)備總內(nèi)存大?。?/p>

double allMemory = [[NSProcessInfo processInfo] physicalMemory]; 
double totalMemory = (allMemory / 1024.0) / 1024.0;

發(fā)現(xiàn)在 iPhone 7 Plus 上得到的值是 3072MB,而 iPhone X 上是 2816MB??赡苁沁@個原因?qū)е?iPhone X 有特殊吧。


結(jié)果出來了,但是是否靠譜呢?實話說,當(dāng)時通過這種方式拿到這個結(jié)果之后,我心里也不是很確定。所以就開始去查找相關(guān)的官方文檔,結(jié)果官方文檔中只找到了 2 篇相關(guān)文檔和 1 個 WWDC2010 的 session:

其中第 1 篇介紹的更多一些,這里我只摘出一部分重要的信息:

The format of a low memory report differs from other crash reports in that there are no backtraces for the application threads. A low memory report begins with a header similar to the Header of a crash report. Following the header are a collection of fields listing system-wide memory statistics. Take note of the value for the Page Size field. The memory usage of each process in a low memory report is reported in terms of number of memory pages.

The most important part of a low memory report is the table of processes. This table lists all running processes, including system daemons, at the time the low memory report was generated. If a process was "jettisoned", the reason will be listed under the [reason] column. A process may be jettisoned for a number of reasons:

  • [per-process-limit]: The process crossed its system-imposed memory limit. Per-process limits on resident memory are established by the system for all applications. Crossing this limit makes the process eligible for termination.

這段對 JetsamEvent 中的內(nèi)容作了簡單的描述,其中提到了 Page Size 字段;每個進(jìn)程所使用的內(nèi)存頁數(shù)量;以及被 kill 時的 [reason] 項,其中 [per-process-limit] 表示進(jìn)程內(nèi)存占用超過了此進(jìn)程的內(nèi)存限制。

通過這部分官方描述,可以與上面的 pageSize、reason 相吻合,但是文檔在提到當(dāng)前進(jìn)程所占用的內(nèi)存頁數(shù)量時,沒有明確指出是 rpages 項。不過在 session 視頻中有提到是 count resident pages:

residentPages.png

所以基于這些只能推測 rpages 應(yīng)該是 resident pages 的縮寫,表示當(dāng)前進(jìn)程占用的內(nèi)存頁數(shù)量(_)。

再次聲明:此結(jié)論不一定準(zhǔn)確,僅供參考。

3. 基于內(nèi)存上限值,能夠指導(dǎo)我們做些什么?

其實這個問題,我也沒想到太多有用的思路,下面是目前我能想到的一些點,僅供參考:

  1. 首先對自己的 APP 內(nèi)存占用情況有個清晰的了解:可以收集 APP 在線上使用過程中占用的最大內(nèi)存值。最好是實時記錄,盡量精確一些。然后根據(jù)收集上來的值分別統(tǒng)計出 1GB 設(shè)備、2GB/3GB 設(shè)備上的內(nèi)存分布。
  2. 如果上面統(tǒng)計到的值中 2GB、3GB 設(shè)備上有不少大于 650M 的情況,那么就需要采取一定的優(yōu)化策略盡量降低 App 的最大內(nèi)存值。比如:去響應(yīng)系統(tǒng)發(fā)出的內(nèi)存警告的通知,做一些內(nèi)存清理工作。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容