最近在研究 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:
- Understanding Low Memory Reports
- Understanding Memory Usage Limits for WatchKit Apps and Extensions
- Understanding Crash Reports on iPhone OS(第18-24分鐘)
其中第 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:

所以基于這些只能推測 rpages 應(yīng)該是 resident pages 的縮寫,表示當(dāng)前進(jìn)程占用的內(nèi)存頁數(shù)量(_)。
再次聲明:此結(jié)論不一定準(zhǔn)確,僅供參考。
3. 基于內(nèi)存上限值,能夠指導(dǎo)我們做些什么?
其實這個問題,我也沒想到太多有用的思路,下面是目前我能想到的一些點,僅供參考:
- 首先對自己的 APP 內(nèi)存占用情況有個清晰的了解:可以收集 APP 在線上使用過程中占用的最大內(nèi)存值。最好是實時記錄,盡量精確一些。然后根據(jù)收集上來的值分別統(tǒng)計出 1GB 設(shè)備、2GB/3GB 設(shè)備上的內(nèi)存分布。
- 如果上面統(tǒng)計到的值中 2GB、3GB 設(shè)備上有不少大于 650M 的情況,那么就需要采取一定的優(yōu)化策略盡量降低 App 的最大內(nèi)存值。比如:去響應(yīng)系統(tǒng)發(fā)出的內(nèi)存警告的通知,做一些內(nèi)存清理工作。