iOS記憶體管理和malloc原始碼解讀
最近由於排查問題,順便對iOS的記憶體管理,尤其是malloc庫稍微深入地瞭解一下,在這裡整理出來,和大家分享一下。
0. iOS記憶體基本原理
在接觸iOS開發的時候,我們都知道“引用計數”的概念,也知道ARC和MRR,但其實這僅僅是對堆記憶體上物件的記憶體管理。用WWDC某Session裡的話說,這其實只是記憶體管理的冰山一角。
在記憶體管理方面,其實iOS和其它作業系統總體上來說是大同小異的,大的框架原理基本相似,小的細節有所創新和不同。
和其它作業系統上執行的程式類似,iOS App程式的地址空間也分為程式碼區、資料區、棧區和堆區等。程式開始時,會把mach-o檔案中的各部分,按需載入到記憶體當中。
而對於一般的iPhone,實際實體記憶體都在1G左右,對於超大的記憶體需求怎麼辦呢?其實這也是和其它作業系統一樣的道理,都由系統核心維護一套虛擬記憶體系統。但這裡需要注意的是iOS的虛存系統原則略有不同,最截然不同的地方就是當實體記憶體緊張情況時的處理。
當實體記憶體緊張時,iOS會把可以通過重新對映來載入的內容直接清理出記憶體,對於不可再生的資料,iOS需要App程式配合處理,向各程式傳送記憶體警告要求配合釋放記憶體。對於不能及時釋放足夠記憶體的,直接Kill掉程式,必要時時甚至是前臺執行的App。
如上所述,iOS在外存沒有交換區,沒有記憶體頁換出的過程。
1. malloc基本原理
在iOS App程式地址空間的各個區域中,最靈活的就要屬堆區了,它為程式動態分配記憶體,也是我們經常和記憶體打交道的地方。
通常,我們會在需要新物件的時候,進行 [NSObject alloc]呼叫,而釋放物件時需要release(ARC會自動幫你做到這些)。
而這些alloc、release方法的呼叫,通常最終都會走到libsystem_malloc.dylib的malloc()和free()函式這裡。libsystem_malloc.dylib是iOS核心之外的一個記憶體庫,我們App程式需要的記憶體,先回請求到這裡,但最終libsystem_malloc.dylib也都會向iOS的系統核心發起申請,對映實際記憶體到App程式的地址空間上。
從蘋果公開的malloc原始碼上來看,malloc的原理大致如下:
malloc記憶體分配基於malloc zone,並將記憶體分配按大小分為nano、tiny、small、large幾種型別,申請時按需進行最適分配
malloc在首次呼叫時,初始化default zone,在64位情況下,會初始化default zone為nano zone,同時初始化一個scalable zone作為helper zone,nano zone負責nano大小的分配,scalable zone則負責tiny、small和large記憶體的分配
每次malloc時,根據傳入的size引數,優先交給nano zone做分配處理,如果大小不在nano範圍,則轉交給helper zone處理。
(截圖自http://www.tinylab.org/memory-allocation-mystery-%C2%B7-malloc-in-os-x-ios/)
下面分別對nano zone和scalable zone上分配記憶體的原始碼做簡要解讀(由於蘋果Open source的程式碼是針對OS X的特定版本,具體細節可能與iOS上有所不同,如地址空間分佈)。
2. nano malloc
在支援64位的條件按下,malloc優先考慮nano malloc,負責對256B以下小記憶體分配,單位是16B。
nano zone分配記憶體的地址空間範圍是0x00006nnnnnnnnnnn(OSX上64位情況),將地址空間從大到小一次分為Magazine、Band和Slot幾個級別。
- Magazine範圍對應於CPU,CPU0對應Mag0,CPU1對應Mag1,依次類推;
- Band範圍為2M,連續記憶體分配當記憶體不夠時以Band為單位向核心請求;
- Slot則對應於每個Band中128K大小的範圍,每個Band都分為16個Slot,分別對應於16B、32B、…256B大小,支援它們的記憶體分配
分配過程:
- 確定當前cpu對應的mag和通過size引數計算出來的slot,去對應metadata的連結串列中取已經被釋放過的記憶體區塊快取,如果取到檢查指標地址是否有問題,沒有問題就直接返回;
- 初次進行nano malloc時,nano zone並沒有快取,會直接在nano zone範圍的地址空間上直接分配連續地址記憶體;
- 如當前Band中當前Slot耗盡則向系統申請新的Band(每個Band固定大小2M,容納了16個128k的槽),連續地址分配記憶體的基地址、limit地址以及當前分配到的地址由meta data結構維護起來,而這些meta data則以Mag、Slot為維度(Mag個數是處理器個數,Slot是16個)的二維陣列形式,放在nanozone_t的meta_data欄位中。
當App通過free()釋放記憶體時:malloc庫會檢查指標地址,如果沒有問題,則以連結串列的形式將這些區塊按大小儲存起來。這些連結串列的頭部放在meta_data陣列中對應的[mag][slot]元素中。
其實從快取獲取空餘記憶體和釋放記憶體時都會對指向這篇記憶體區域的指標進行檢查,如果有類似地址不對齊、未釋放/多次釋放、所屬地址與預期的mag、slot不匹配等情況都會以報錯結束。
下圖是我根據個人理解梳理出來的一個關係圖,圖中標出了nanozone_t、meta_data_t等相關結構的關鍵欄位畫了出來(OSX)。
除了分配和釋放,系統記憶體吃緊時,nano zone需將cache的記憶體區塊還給系統,這主要是通過對各個slot對應的meta data上掛著的空閒連結串列上記憶體區塊回收來完成。
3. scalable zone上記憶體分配簡要分析
對於超出nano大小範圍或者不支援nano分配的,直接會在scalable zone(下文簡稱szone)上分配記憶體。由於szone上的記憶體分配比起nano分配要較為複雜,細節繁多,下面僅作簡要介紹,感興趣的同學可以直接閱讀原始碼。
在szone上分配的記憶體包括tiny、small和large三大類,其中tiny和small的分配、釋放過程大致相同,large型別有自己的方式管理。
而tiny、small的方式也依然遵循nano分配中的原則,新記憶體從系統申請並分配,free後按照大小以特定的形式快取起來,供後續分配使用。這裡的分配在region上進行,region和nano malloc裡的band概念極為相似,但不同的是地址空間未必連續,而且每個region都有自己的點陣圖等描述資訊。和nano,一樣每個cpu有一個magazine,除此之外還分配了一個index為-1的magazine作為後備之用。
下面是一個簡圖。
以tiny的情況為例,分配時:
- 確定當前執行緒所在處理器的magazine index,找到對應的magazine結構。
- 優先檢視上次最後釋放的區塊是否和此次請求的大小剛好相等(都是對齊之後的slot大小),如果是則直接返回。
- 如果不是,則查詢free list中當前請求大小區塊的空閒快取列表,如果有返回,並整理列表。
- 如果沒有,則在free list找比當前申請區塊大的,而且最接近的快取,如果有返回,並把剩餘大小放到free list中另外的連結串列上。(這裡需要注意的是,在一般情況下,free list分為64個槽,0-62上掛載區塊的大小都是按16B為單位遞增,63為所有更大的記憶體區塊掛載的地方)
- 上面幾項都不行,就在最後一個region的尾部或者首部(如果支援內部ALSR)找空閒區域分配。
- 如果還是不行,說明所有現有region都沒空間可用了,那麼從一個後備magazine中取出一個可用region,完整地拿過來放到當前magazine,再走一遍上面的步驟。
- 如果這都不成,那隻能向核心申請一塊新的region區域,掛載到當前的magazine下並分配記憶體。
- 要是再不行就沒招了,系統也給不到記憶體,就報錯返回。
free時:
- 檢查指標指向地址是否有問題。
- 如果last free指標上沒有掛載記憶體區塊,則放到last free上就OK了。
- 如果有last free,置換記憶體,並把last free原有記憶體區塊掛載到free list上(在掛載的free list前,會先根據region點陣圖檢查前後區塊是否能合併成更大區塊,如果能會合併成一個)。
- 合併後所在的region如果空閒位元組超過一定條件,則將把此region放到後備的magazine中(-1)。
- 如果整個region都是空的,則直接還給系統核心,一了百了。
而large的情況,malloc以頁為單位申請和分配記憶體,不區分magazine,szone統一維護一個hash table管理已申請的記憶體。而且由於記憶體區域都比較龐大,只快取總量2G的區塊,分為16個元素,每個最大為128M。large相關的結構相對簡單,就不特意畫圖了。
綜上,iOS記憶體管理和malloc庫的原始碼整理到此。如果發現分析得有紕繆的地方或者描述不完整的請不吝指出,歡迎隨時交流。
附上蘋果malloc原始碼:
http://www.opensource.apple.com/source/libmalloc/libmalloc-53.1.1/src/
該文章來自於阿里巴巴技術協會(ATA)
作者:去疾
相關文章
- iOS探索 記憶體對齊&malloc原始碼iOS記憶體原始碼
- Flutter引擎原始碼解讀-記憶體管理篇Flutter原始碼記憶體
- Linux記憶體管理:MallocLinux記憶體
- ThreadLocal原始碼解讀和記憶體洩露分析thread原始碼記憶體洩露
- iOS記憶體管理詳解iOS記憶體
- 記憶體管理原始碼 (轉)記憶體原始碼
- struct和malloc記憶體互轉例子Struct記憶體
- Python原始碼閱讀-記憶體管理機制(一)Python原始碼記憶體
- Python原始碼閱讀-記憶體管理機制(二)Python原始碼記憶體
- Memcached記憶體管理原始碼分析記憶體原始碼
- iOS 記憶體管理iOS記憶體
- 讀Flink原始碼談設計:有效管理記憶體之道原始碼記憶體
- 理解 iOS 和 macOS 的記憶體管理iOSMac記憶體
- JVM原始碼分析之堆外記憶體完全解讀JVM原始碼記憶體
- 記憶體分配詳解 malloc, new, HeapAlloc, VirtualAlloc,GlobalAlloc記憶體
- “理解”iOS記憶體管理iOS記憶體
- iOS 記憶體管理MRCiOS記憶體
- iOS 記憶體管理研究iOS記憶體
- iOS arc 記憶體管理iOS記憶體
- iOS 中的記憶體管理iOS記憶體
- iOS記憶體管理相關iOS記憶體
- 理解 iOS 的記憶體管理iOS記憶體
- iOS記憶體管理淺析iOS記憶體
- iOS 進階—— iOS 記憶體管理 & BlockiOS記憶體BloC
- 筆記-更深層次的瞭解iOS記憶體管理筆記iOS記憶體
- Axios 原始碼解讀iOS原始碼
- Python記憶體管理機制-《原始碼解析》Python記憶體原始碼
- Paddle原始碼之記憶體管理技術原始碼記憶體
- 關於c語言記憶體分配,malloc,free,和段錯誤,記憶體洩露C語言記憶體洩露
- PostgreSQL 原始碼解讀(226)- Linux Kernel(虛擬記憶體)SQL原始碼Linux記憶體
- iOS內功篇:記憶體管理iOS記憶體
- iOSARC記憶體管理要點iOS記憶體
- iOS另類的記憶體管理iOS記憶體
- 【記憶體管理】Oracle AMM自動記憶體管理詳解記憶體Oracle
- spark 原始碼分析之十五 -- Spark記憶體管理剖析Spark原始碼記憶體
- C++動態記憶體管理與原始碼剖析C++記憶體原始碼
- JVM原理講解和調優,記憶體管理和垃圾回收,記憶體調優JVM記憶體
- 記憶體動態分配與釋放,malloc和new區別記憶體