iPhone 做爲一個移動設備,其計算和內存資源一般是很是有限的,而許多用戶對應用的性能卻很敏感,卡頓、應用回到前臺丟失狀態、甚至 OOM 閃退,這就給了 iOS 工程師一個很大的挑戰。html
網上的絕大多數關於 iOS 內存管理的文章,大可能是圍繞 ARC/MRC、循環引用的原理或者是如何找尋內存泄漏來展開的,而這些內容更準確的說應該是 ObjC 或者 Swift 的內存管理,是語言層面帶來的特性,而不是操做系統自己的內存管理。git
若是咱們須要聊聊」管理「內存,那麼就須要先了解一些基礎知識。github
一個設備的 RAM 大小。如下是維基百科上的資料:web
簡單來講,iPhone 8(不包括 plus) 和 iPhone 7(不包括 plus)及以前都是 2G 內存,iPhone 6 和 6 plus 及以前都是 1G 內存。面試
每一個進程都有一個本身私有的虛擬內存空間。對於32位設備來講是 4GB,而64位設備(5s之後的設備)是 18EB(1EB = 1000PB, 1PB = 1000TB),映射到物理內存空間。緩存
內存管理、映射中的基本單位是頁,一頁的大小是 4kb(早期設備)或者 16kb(A7 芯片及之後)網絡
由於有頁的存在,每次申請內存都必須以頁爲單位。然而這樣一來,若是隻是申請幾個 byte,卻不得不分配一頁(16kb),是很是大的浪費。所以在用戶態咱們有 「heap」 的概念。session
因爲虛擬內存的空間遠遠大於物理內存,在任意一個時間點,虛擬內存中的一個頁並不必定老是在物理內存中,而是可能被暫時存到了磁盤上,這樣物理內存即可以暫時釋放這部分空間,供優先級更高的任務使用,所以磁盤能夠做爲 backing store 以擴展物理內存(MacOS 中有,iOS 沒有)。另外一種多是加載一個比較大的文件/動態庫,每次使用咱們可能只須要加載其中的一部分,那麼就可使用 mmap 映射這個文件到虛擬內存空間,這樣當咱們訪問其中一部分時,系統會自動把這一部分從磁盤加載到內存,而不加載其他部分。數據結構
這樣把磁盤中的數據寫到內存/從內存中寫回磁盤成爲 page in/out。架構
沒法被 page out 的內存,主要爲系統層所用,開發者不須要考慮這些。
一個 VM Region 是指一段連續的內存頁(在虛擬地址空間裏),這些頁擁有相同的屬性(如讀寫權限、是不是 wired,也就是是否能被 page out)。舉幾個例子:
每一個 VM Region 對應一個數據結構,名爲 VM Object。Object 會記錄這個 Region 內存的屬性
當前正在物理內存中的頁(沒有被交換出去)
在 iOS 上管理殺進程釋放資源策略模塊叫作 Jetsam,這裏推薦五子棋的文章,其中有詳細的介紹。
蘋果官方關於 OOM 的文檔和接口很是少,以致於 facebook 在判斷應用是否上次由於 OOM 而閃退時,須要通過一個漫長的邏輯判斷,當不知足全部條件時才能斷定爲 OOM(想象一下若是系統能提供一個接口,告訴開發者上次的退出緣由,會方便多少!)
咱們會好奇,當一個普通 app 啓動時,內存消耗究竟有多少?
咱們剛討論到內存的不一樣類別,那麼應該選用哪一個值做爲內存佔用量的標準呢?
在 WWDC13 704 中,蘋果推薦用 footprint 命令來查看一個進程的內存佔用。
關於什麼是 footprint,在官方文檔 Minimizing your app’s Memory Footprint 裏有說明:
Refers to the total current amount of system memory that is allocated to your app.
因爲該命令只能在 MacOS 上運行,而且 iOS 上也沒有 Activity Monitor,咱們新建一個 Mac app,而後用不一樣手段測量內存佔用
能夠看到,Xcode、系統、footprint 工具和 phys_footprint 獲得的數據是一致的,而既然官方推薦了 footprint,所以咱們以這幾個方法獲得的結果做爲標準。猜想 footprint 比 Instruments 數據更大的緣由是存在一些」非代碼執行開銷「,如把系統和應用二進制加載進內存。iOS 中雖然不能使用系統 Activity Monitor 和 footprint 命令,也能在 Xcode 中和 phys_footprint 獲得一樣的結果。
至此咱們能夠獲得一個結論: Instruments 中顯示的部分,其實也只是整個應用進程裏內存的一部分。可是因爲咱們可以控制的只有這一部分,所以應該把精力投入到 Instruments 的分析中去。
應用的詳細性能分析老是須要依賴 Instruments 的強大功能。從 Allocations 角度來看,總的內存佔用 = All Heap Allocations + All Anonymous VM:
主要包含一些系統模塊的內存佔用。有些部分雖然看起來離咱們的業務邏輯比較遠,但實際上是保證咱們代碼正常運行不可或缺的部分,也是咱們經常忽視的部分。通常包括:
咱們平時最常常會作的 debug 之一,就是查找循環引用。而循環引用形成的 leak 數據一般是 UIKit 或咱們本身的一些數據結構,會被歸類到 heap。這些是咱們相對熟悉的,網上也有很是多的文章,這裏再也不討論。而就 VM 這塊來講,由於不受咱們直接控制,文檔也較少,因此相對神祕一些,每每容易被忽視。
對於 VM 中的線程棧開銷、網絡 buffer 等,咱們其實沒有太大的控制能力,一般這些也不會是內存開銷的主要緣由(除非有成百上千的線程和頻繁大量的網絡請求)。而對於即刻和絕大多數 app 來講,尤爲是採用了 AsyncDisplayKit(用空間換時間)的狀況下,渲染開銷是絕對不可忽視的一塊。
我一直認爲,移動設備上無論是 CPU、GPU 仍是內存,最大的性能殺手必定是佈局和渲染。佈局數據和通常數據結構相似,單個內存開銷最多以 KB 計,而渲染緩存很容易就用「兆」來計算,更容易影響到總體開銷。
任意打開一個 app,能夠看到渲染無非就是兩大部分:圖片和文字。
咱們知道,解壓後的圖片是由無數像素數據組成。每一個像素點一般包括紅、綠、藍和 alpha 數據,每一個值都是 8 位(0–255),所以一個像素一般會佔用 4 個字節(32 bit per pixel。少數專業的 app 可能會用更大的空間來表示色深,消耗的內存會相應線性增長)。
下面咱們來計算一些一般的圖片開銷:
有了大體的概念,之後看到一張圖能簡單預估,大概會吃掉多少內存。
圖片解碼是每一個開發者都繞不過去的話題。圖片從壓縮的格式化數據變成像素數據須要通過解碼,而解碼對 CPU 和內存的開銷都比較大,同時解碼後的數據如何管理,如何顯示都是須要咱們注意的。
Share hardware-accelerated buffer data (framebuffers and textures) across multiple processes. Manage image memory more efficiently.
網上關於渲染的資料不少,可是不少都是人云亦云,咱們來講一些比較少討論的點:
做爲 backing image 的 CGImage
Offscreen rendering is invoked whenever the combination of layer properties that have been specified mean that the layer cannot be drawn directly to the screen without pre- compositing. Offscreen rendering does not necessarily imply software drawing, but it means that the layer must first be rendered (either by the CPU or GPU) into an offscreen context before being displayed.
關於文字渲染的文檔資料並非不少,所以咱們須要作一些實驗來判斷。 新建一個項目,添加一個全屏的 label,不停切換文字,獲得 cpu 佔用率穩定在 15%,gpu佔用率 0%。而且 Time Profiling 顯示
排名第一的方法主要是在調用 render_glyphs,說明主要是 CPU 參與了文字渲染。
理論上 iPhone X 全屏有 1125 * 2436 = 2740500 個像素,距離實際佔用內存很是接近,只多了 143084 byte(139.73kb),說明差很少正好是一個像素對應一個字節。這印證了 WWDC(WWDC18 219和416)上的結論,即黑白的位圖只佔用 1 個字節,比 4 字節節省 75% 的空間。固然實際使用過程當中很難限制文字只採用黑白兩種顏色,可是仍是應該瞭解蘋果的優化過程。
在以上測試基礎上,若是咱們嘗試把第一個字符加上紅色屬性,或者添加 emoji,那麼渲染結果就再也不是黑白的了,而是一張彩色圖片,相似普通圖片那樣每一個像素須要 4 個字節來描述。所以理論上所消耗的內存會變成 2.75MB * 4 = 10MB 多一點。測試獲得:
結果佔用了 11468800 bytes,是原來 2740500 的 3.97 倍,與理論值 4 很是接近(可能內存中還存在一些附屬的其餘元數據,而這些不會如同像素數據同樣線性放大,所以不徹底是精確 4 倍關係)。比較好的印證了以前的結論。
整整一屏的文字,在 3x 設備上,只佔用了 2MB 多一點的內存,能夠說是很是省了。
iOS 的內存管理有如下幾個特色:
做者:即刻技術團隊
連接:https://juejin.im/post/5bec0e...