iOS中的內存管理

先來看下蘋果文檔:

memory_management.png

Objective-C提供兩種方式的內存管理方式:

  • 手動管理(“manual retain-release” or MRR)
  • 自動引用計數方式(Automatic Reference Counting, or ARC)

內存管理一般出現的問題:

  • Freeing or overwriting data that is still in use
    This causes memory corruption, and typically results in your application crashing, or worse, corrupted user data.

  • Not freeing data that is no longer in use causes memory leaks
    A memory leak is where allocated memory is not freed, even though it is never used again. Leaks cause your application to use ever-increasing amounts of memory, which in turn may result in poor system performance or your application being terminated

內存管理的基本法則:

  • You own any object you create(自己生成的對象,自己持有)
  • You can take ownership of an object using retain(非自己生成的對象,自己也能持有)
  • When you no longer need it, you must relinquish ownership of an object you own(不再需要自己持有的對象時釋放)
  • You must not relinquish ownership of an object you do not own(無法釋放非自己持有的對象)

去年參加了一個技術分享,講的是iOS內存管理及優化
引子就很吸引人:

  • 桌面系統中很少有應用因為使用內存過多而被Kill掉,為啥iOS會呢?
  • 虛擬內存為何物?為啥有時它能超過物理總內存?虛擬內存占用過高會引來內存警告嗎?
  • Allocations中的Dirty Size和Resident Size分別指的是什么?All Heap & Anonymous VM是什么?
  • iOS內存管理機制是什么樣的?它基于什么原則來Kill掉進程的?
  • 內存有分類嗎?什么類型的內存可以回收?
  • 我們了解自己的程序嗎?什么地方占用內存多,什么地方可以優化?如何避免內存峰值過高?

程序員對內存的關注點:

  • 正確使用(1.非法訪問 2.內存泄露)

  • 高效使用(1.降低內存峰值 2.處理內存警告 3.Cache)

  • 內存管理的歷史

    • 邏輯地址 VS 物理地址
      程序訪問的都是邏輯地址,邏輯地址需要經過轉換之后才能訪問物理地址。CPU訪問先通過界限寄存器的對比,如果越界就報越界錯誤,否則加上基址寄存器的值,然后構成物理地址.


      邏輯地址轉換物理地址.png
      • Swap
        當物理內存不夠用時,可以將不用的進程放到磁盤去,騰出內存空間給新的進程.相當于通過通過輔存(磁盤)來擴充實際的物理內存.
        Swap.png
  • 虛擬地址
    虛擬地址相當于邏輯地址.虛擬地址到物理地址是通過CPU內部的內存管理單元(MMU)處理.32位系統,虛擬地址為4GB,64位系統為16GGB.

    CPU處理示意圖

    虛擬地址與物理內存或后備存儲的對應

  • 段式虛擬內存
    以前的內存分配空間為整個進程空間,現在可以將其分為小單位的段以提高利用率,以前的連續分配改為離散分配,以前的無權限分區改為按邏輯分配權限.

    段式虛擬內存

段式虛擬內存的轉換過程

段式虛擬內存的轉換過程分為兩部分:段號和段內偏移.系統有一個全局的段表.先通過段號去段表里查基值和界限,然后加載到基址寄存器和界限寄存器上.然后在經過轉換訪問物理地址.

  • 頁式虛擬內存
    段式虛擬內存分配的最小單位是段,但相對來講還是比較大(幾兆).段與段之間可能會產生外部碎片.頁式虛擬內存用來解決外部碎片,將虛擬地址和物理地址劃分成等大小的頁框(4KB或8KB,iOS中為4KB).通過等大小的頁框來做映射關系.可以理解為段式虛擬內存的特例,所有段都等大小。有個特點就是頁錯誤,當訪問物理地址中的一個沒有做映射的地址時會觸發一個中斷,操作系統會接管這個中斷,將這個頁在輔存中的內容讀取到物理頁,然后在建立映射關系,然后再恢復現場,程序無感知.
    頁式虛擬內存
頁式虛擬內存的訪問過程
  • 程序內存分布

    程序的內存結構

    可執行文件里面有個頭,里面記錄著所有段的大小,進程加載器會根據頭將各個段加載到物理內存去,比如代碼段和數據段,有些段在可執行文件里面只是個占位符,實際加載到虛擬內存上才會分配內存.數據段是初始過的全局變量和靜態變量.未初始化的全局變量和靜態變量放在bss段.堆從地地址到高地地址,一般用malloc分配.棧從高地址到低地址,用來存儲局部變量或者函數調用時候用到.

  • iOS中的內存段

    • _PAGEZERO 固定分配在零地址,一個頁大小.沒有訪問權限,用于零地址出發exception.
    • _TEXT 代碼段
    • _DATA 數據段
    • __MALLOC_TINY 堆地址,和以下兩個只是大小區別,小于一個頁大小,分配到TINY段里面
    • __MALLOC_SMALL 大于一個頁小于一兆
    • __MALLOC_LARGE 大于一兆
      關于iOS的內存分類可以閱讀 Finding iOS memory
  • iOS內存管理

    iOS內存管理

    iOS使用全功能的內存管理模式,有端式和頁式.

  • 桌面系統中很少有應用因為使用內存過多而被Kill掉,為啥iOS會呢?
    因為iOS上沒有Swap機制.(1.移動設配的閃存容量有限2.閃存的寫次數有限,頻繁寫會降低壽命)

  • 思考:代碼是要加載到內存執行的,如果沒有Swap機制那代碼很大的程序豈不是很占內存?

  • 低內存處理機制Jetsam

    • 基于優先隊列,從上往下優先級越高.
      Screen Shot 2016-04-12 at 9.29.11 PM.png

      當系統內存過低時就會廣播消息,大家盡量去釋放內存.過一段時間后,內存還不夠用時,就是從上往下Kill進程.
    • UIKit提供三種通知方式:
      • [UIApplicationDelegate applicationDidReceiveMemoryWarning:]
      • [UIViewController didReceiveMemoryWarning:]
      • UIApplicationDidReceiveMemoryWarningNotification
    • 內存警告消息來自主線程,應避免主線程這時候卡頓后者分配過大內存或者快速分配(騰訊Buddly可以檢測主線程卡頓)
    • 如果App因為內存警告被Kill掉,會生成LowMemory***.log
  • 內存的分類

    • Clean Memory 在閃存中有備份,能再次讀取重建
      • Code,framework,memory-mapped files
    • Dirty Memory 所有非Clear Memory,系統無法回收
      • Heap allocations,decompressed images,caches
        例子:

NSString *str1 = [NSString stringWithString:@"Welcome!"]; //堆分配的內存 Dirty Memory
NSString *str2 = @"Welcome!"; //常量字符串,存放在一個只讀數據段里面,這段內存釋放后,還可以在讀取重建 Clear Memory
char *buf = malloc(100 * 1024 *1024); // Clear Memory分配100M虛擬內存,當沒有用時沒有建立映射關系
for (int i = 0; i < 3 * 1024 * 1024; ++i) {
buf[i] = rand();
}

     關于Clear Memory和Dirty Memory的介紹 [What is resident and dirty memory of iOS?](http://stackoverflow.com/questions/13437365/what-is-resident-and-dirty-memory-of-ios)   
* Dirty & Resident & Virtual Memory  
    * 虛擬內存層面
        * Virtual Memory = Clear Memory + Dirty Memory   
    * 物理內存層面  
        * Resident Memory = Clean Memory(Loaded in Physical Memory) + Dirty Memory  
    * 物理頁面的生命周期   
     根據狀態來劃分  
       * Free(空閑) 物理頁沒被任何虛擬內存使用   
       * Active(活躍) 物理頁正用于一個虛擬內存頁,并且最近被引用過,這種頁面一般不會被交換出去  
       * Inactive(非活躍)物理頁正用于一個虛擬內存頁,但最近沒有被引用過,這種頁面有可能被交換出去  
       * Speculative(投機) 針對可能的內存需要做了一個猜測的分配,對頁面進行投機映射,因為很可能很快被訪問到  
       * Wired(聯動) 物理頁正用于一個虛擬內存頁,但不能被交換出去,一般用于內核代碼數據  
![物理頁面的生命周期](http://upload-images.jianshu.io/upload_images/1736329-5490b505dd10a15b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)   

* 內存的分析工具Allocations   
![Allocations](http://upload-images.jianshu.io/upload_images/1736329-32f9220e8c5f9856.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)   
All Heap & Anonymous VM中All Heap為堆上分配的對象,Anonymous VM比如創建UIView時CALayer底層所占空間.Diry Size為Dirty Memory所占內存,Resident Size為實際所占的物理內存.   

* 內存最佳實踐  
   * Weak Strong Dance(解決block循環引用的技巧)  
   AFNetworking中的實踐:  
__weak __typeof(self)weakSelf = self;   

AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};
}];

可以查看[對Weak String Dance的思考](http://www.lxweimin.com/p/4ec18161d790)  
   * Dealloc Block Executor(釋放內存的小技巧)  
![Dealloc Block Executor](http://upload-images.jianshu.io/upload_images/1736329-9fa7d457e2a0bd5f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)  
AssociateObject的父對象釋放的時候,子對象也會被釋放.通過block來釋放對象,不用使用dealloc.  
![UIView的釋放時序圖](http://upload-images.jianshu.io/upload_images/1736329-f39d8cf05106499b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    * 降低內存峰值  
       * Lazy Allocation 

MyBuffer *GetGlobalBuffer()
{
static MyBuffer *sMyBuffer = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sMyBuffer = [MyBuffer new];
});
return sMyBuffer;
}

直到使用的時候才分配,且為線程安全,可以用于分配對象或者資源文件讀取等,方便Patch,比如JSPatch.
       * alloca VS malloc 
棧內存分配alloca(size_t)
          * 棧分配僅僅修改棧指針寄存器,比malloc遍歷并修改空閑列表要快得多  
          * 棧內存一般都已經在物理內存中,不用擔心頁錯誤  
          * 函數返回的時候棧分配的空間都會自動釋放   
          * 但僅適合小空間的分配,并且函數嵌套不宜過深   
       * calloc VS malloc + memset  
calloc(size_t num,size_t size)分配內存時是虛擬內存,只有在訪問的時候才會發生物理頁的映射關系,malloc+memset就會產生Dirty Memory.
          * 分配內存并初始化
          * 立即分配虛擬空間并設置清0標記位,但不分配物理內存
          * 只有相應的虛擬地址空間被讀寫操作的時候才需要分配相應的物理內存頁并初始化   
       * AutoreleasePool
          * 基于引用計數,Pool執行drain方法會release所有該Pool中的autorelease對象
          * 可以嵌套多個AutoReleasePool
          * 每個線程并沒有設置默認的AutoReleasePool,需要手動創建,避免內存泄露  
          * 在一段內存分配頻繁的代碼中嵌套AutoReleasePool有利于降低整體內存峰值  
       * imageNamed VS imageWithContentOfFile  
          * imageNamed使用系統緩存,適用于頻繁使用的小圖片  
          * imageWithContentOfFile不帶緩存機制,適用于大圖片,使用完就釋放
       * NSData with fileMapping
NSData & 內存映射文件,NSData有兩種讀取方式:
            * [NSData dataWithContentsOfFile:path];
            * [NSData dataWithContentsOfFile:path options:NSDataReadingMappedIfSafe error:&error];映射文件到虛擬內存,只有讀取操作的時候才會讀取相應頁的內容到物理內存頁中,常用語大文件中.
    * NSCache & NSPurgableData
        * NSCache 
            * 2種界限條件:totalCostLimt & countLimit 超過這兩種界限時都會去釋放一些舊的資源.
            * 類NSMutableDictionary,setObject:forKey:cost
            * evictsObjectWithDiscardContent & <NSDicardableContent>
            * 最好監聽內存警告消息并移除所有Cache
        * NSPurgableData
            * 當系統處于低內存的時候自動移除
            * 適用于大數據
     * 內存警告的處理
         * 盡可能釋放多資源,尤其圖片等占內存多的資源,等需要用的時候再重建
         * 單例對象不要創建之后就一直持有數據,在內存緊張的時候釋放掉
         * iOS6之后系統內存緊張會自動釋放CALayer的CABackingStore對象,需要使用的時候在調用drewRect來構建,所以沒必要將self.view = nil,但有時候對于隱藏的ViewController直接設置self.view = nil能簡化代碼邏輯  
示例代碼: 
 

@interface ViewController ()
@property (strong,readonly)NSString testData;
@end
@implementation ViewController
@synthesize testData=_testData;
// Override the default getter for testData
-(NSString
)testData
{
if(nil==_testData)
_testData=[self createSomeData];
return _testData;
}

  • (void)didReceiveMemoryWarning
    {
    [super didReceiveMemoryWarning];
    _testData=nil;
    }
   * 業內趨勢
        * 內存壓縮
        * 地址空間布局隨機化ASLR
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容

  • 2016年國慶假期終于把此書過完,整理筆記和體會于此。 關于書名 書名源于俄羅斯的演員斯坦尼斯拉夫斯基創作的《演員...
    李劍飛的簡書閱讀 7,268評論 2 65
  • 概述 我們都知道一個進程是與其他進程共享CPU和內存資源的。正因如此,操作系統需要有一套完善的內存管理機制才能防止...
    SylvanasSun閱讀 3,879評論 0 25
  • 1 內存尋址 1.1 物理地址、虛擬地址以及線性地址 物理地址: 物理內存的內存單元地址 虛擬地址: 程序員看到的...
    瘋狂小王子閱讀 2,862評論 3 21
  • 其實,iOS的內存管理和其它操作系統大同小異。這里按照蘋果文檔所述,重點對堆內存分配整理下。 首先,iOS和其它系...
    xhwASS閱讀 261評論 0 0
  • iOS中的內存管理 內存管理的思考方式 自己生成的對象,自己持有 非自己生成的對象,自己也能持有 不再需要自己持有...
    justvon閱讀 292評論 0 0