iOS 內(nèi)存相關(guān)的基礎(chǔ)知識

為什么不能直接訪問物理內(nèi)存?

  1. 內(nèi)存不夠用。
  2. 內(nèi)存數(shù)據(jù)不安全。

內(nèi)存管理方案

相關(guān)知識點(diǎn)

  • taggedPointer: 專門用來處理小對象,例如NSNumber、NSDate、小NSString等
  • Nonpointer_isa: 非指針類型的isa
  • SideTables:散列表,包括引用計數(shù)列表和弱引用表
  1. TaggedPointer
    TaggedPointer 是一種高效節(jié)省內(nèi)存空間的方法,是一個特別的指針,它分為兩部分:

一部分直接保存數(shù)據(jù) ;
另一部分作為特殊標(biāo)記,表示這是一個特別的指針,不指向任何一個地址
先看看原有的對象為什么會浪費(fèi)內(nèi)存?

假設(shè)存儲一個 NSNumber 對象,值是一個整數(shù)。
正常情況下,如果這個整數(shù)只是一個 NSInteger 的普通變量,那么它所占用的內(nèi)存是與 CPU 的位數(shù)有關(guān),在 32 位 CPU 下占 4 個字節(jié),在 64 位 CPU 下是占 8 個字節(jié)的。
而指針類型的大小也與 CPU 位數(shù)相關(guān), 32 位下為 4 個字節(jié),64 位下是 8 個字節(jié)。

為了改進(jìn)上面提到的內(nèi)存占用和效率問題,蘋果提出了Tagged Pointer對象。
由于 NSNumber、NSDate 一類的變量本身的值需要占用的內(nèi)存大小常常不需要 8 個字節(jié)


NSNumber之Tagged Pointer
  • Tagged Pointer專門用來存儲小的對象,例如NSNumber和NSDate
  • Tagged Pointer指針的值不再是地址了,而是真正的值。實際上它不再是一個對象了,只是一個披著對象皮的普通變量而已。所以,它的內(nèi)存并不存儲在堆中,也不需要 malloc 和 free。
  • 在內(nèi)存讀取上有著 3 倍的效率,創(chuàng)建時比以前快 106 倍。

EX

  • NSCFConstantString:字符串常量,是一種編譯時常量,retainCount值很大,對其操作,不會引起引用計數(shù)變化,存儲在字符串常量區(qū)
  • NSCFString:是在運(yùn)行時創(chuàng)建的NSString子類,創(chuàng)建后引用計數(shù)會加1,存儲在堆上
  • NSTaggedPointerString:標(biāo)簽指針,是蘋果在64位環(huán)境下對NSString、NSNumber等對象做的優(yōu)化。

對于NSString對象來說,當(dāng)字符串是由數(shù)字、英文字母組合且長度小于10時,會自動成為NSTaggedPointerString類型,存儲在常量區(qū)。
當(dāng)有中文或者其他特殊符號時,會直接成為__NSCFString類型,存儲在堆區(qū)

對于NSString來說,當(dāng)字符串較小時,建議直接通過@""初始化,因為存儲在常量區(qū),可以直接進(jìn)行讀取。會比WithFormat初始化方式更加快速

NSNumber 存儲的數(shù)據(jù)不大時,NSNumber *指針是偽指針Tagged Pointer;
NSNumber存儲的數(shù)據(jù)很大時,NSNumber * 指針一般指針,指向NSNumber 實例的地址

  1. NONPOINTER_ISA

nonpointer:表示是否對 isa 指針開啟指針優(yōu)化
0:純isa指針,1:不?是類對象地址,isa 中包含了類信息、對象的引?計數(shù)等
extra_rc:表示該對象的引?計數(shù)值,如對象的引?計數(shù)為10,那么extra_rc為9。如果引?計數(shù)?于10,則需要使用到has_sidetable_rc

  1. 散列表(sideTables)
    參考
    在 runtime 中,有四個數(shù)據(jù)結(jié)構(gòu)非常重要,分別是 SideTables,SideTable,weak_table_t和weak_entry_t。它們和對象的引用計數(shù),以及 weak引用 相關(guān)

四個數(shù)據(jù)結(jié)構(gòu)的關(guān)系?

在 runtime 內(nèi)存空間中,SideTables是一個8個元素長度 的hash數(shù)組,里面存儲了 SideTable。SideTables 的 hash鍵值 就是一個 對象obj的 address。
因此可以說,一個obj對應(yīng)了一個 SideTable。但是一個 SideTable會對應(yīng)多個 obj。因為 SideTable 的數(shù)量只有64個,所以會有很多 obj 共用同一個 SideTable(如果是真機(jī)環(huán)境下最大就是 8 張表)
SideTables 是多張表的形式,就是考慮到性能問題,當(dāng)所有對象都共用一張表的話,因為要考慮到多線程的問題,當(dāng)對引用計數(shù)操作的時候就會對表的加鎖和關(guān)鎖,會比較消耗性能,當(dāng)使用多張表的時候,系統(tǒng)可以根據(jù)一定的算法,對不使用的表進(jìn)行內(nèi)存回收,而不是持續(xù)占用空間。但是也不能每個對象開一張表,因為開表的內(nèi)存太大了,對象很多的話就會有很多的內(nèi)存開辟與回收,也會很消耗性能。所以表的數(shù)量要在一個合理的范圍內(nèi)。

SideTable

而在一個 SideTable 中,又有兩個成員,分別是

RefcountMap refcnts;        // 對象引用計數(shù)相關(guān) map
weak_table_t weak_table;    // 對象弱引用相關(guān) table
  • 其中,refcents 是一個 hash map,其key是obj的地址,而value,則是obj對象的引用計數(shù)。
  • 而 weak_table 則存儲了 弱引用obj 的指針的地址,其本質(zhì)是一個以 obj 地址為 key,弱引用obj 的指針的地址作為 value 的 hash表。hash表 的節(jié)點(diǎn)類型是 weak_entry_t

一個NSObject對象占用多少字節(jié)?

系統(tǒng)分配了16個字節(jié)給NSObject對象(通過malloc_size函數(shù)獲得),但是NSObject對象內(nèi)部只使用了8個字節(jié)空間

有內(nèi)存對齊的原因,結(jié)構(gòu)體的大小必須是最大成員大小(16)的倍數(shù)

截屏2021-09-16 下午11.46.28.png

內(nèi)存閾值

Apple 并沒有準(zhǔn)確的文檔說明每個設(shè)備的內(nèi)存限制。對于設(shè)備的內(nèi)存 OOM 閾值大概有以下幾個方法獲取。這里獲取的限制最好是在重啟 iPhone 以后,使得設(shè)備清空 RAM 緩存。

  1. 方法一: Jetsam 日志

Jetsam 機(jī)制可以理解為操作系統(tǒng)為控制內(nèi)存資源過度使用而采用的一種管理機(jī)制。Jetsam是一個獨(dú)立運(yùn)行的進(jìn)程,每個進(jìn)程都有一個內(nèi)存閾值,一旦超過這個閾值,Jetsam將立即殺死該進(jìn)程。

當(dāng)我們的應(yīng)用被 Jetsam 機(jī)制殺死時,手機(jī)會生成系統(tǒng)日志。在手機(jī)系統(tǒng)設(shè)置隱私分析中,找到以 JetSamEvent. 的開頭的系統(tǒng)日志。在這些日志中,你可以獲取一些關(guān)于應(yīng)用程序的內(nèi)存信息??梢栽谌罩镜拈_頭,看到了pageSize,并找到了 perprocesslimit 項(不是所有日志都有,但是可以找到它)

從 Jetsam 日志中通過使用項目的 rpages * pageSize 可以得到 OOM 的閾值。

{"bug_type":"298","timestamp":"2020-10-15 17:29:58.79
 +0100","os_version":"iPhone OS 14.2
 (18B5061e)","incident_id":"B04A36B1-19EC-4895-B203-6AE21BE52B02"
}
{
  "crashReporterKey" :
 "d3e622273dd1296e8599964c99f70e07d25c8ddc",
  "kernel" : "Darwin Kernel Version 20.1.0: Mon Sep 21 00:09:01
 PDT 2020; root:xnu-7195.40.113.0.2~22\/RELEASE_ARM64_T8030",
  "product" : "iPhone12,1",
  "incident" : "B04A36B1-19EC-4895-B203-6AE21BE52B02",
  "date" : "2020-10-15 17:29:58.79 +0100",
  "build" : "iPhone OS 14.2 (18B5061e)",
  "timeDelta" : 7,
  "memoryStatus" : {
  "compressorSize" : 96635,
  "compressions" : 3009015,
  "decompressions" : 2533158,
  "zoneMapCap" : 1472872448,
  "largestZone" : "APFS_4K_OBJS",
  "largestZoneSize" : 41271296,
  "pageSize" : 16384,
  "uncompressed" : 257255,
  "zoneMapSize" : 193200128,
  "memoryPages" : {
    "active" : 45459,
    "throttled" : 0,
    "fileBacked" : 34023,
    "wired" : 49236,
    "anonymous" : 55900,
    "purgeable" : 12,
    "inactive" : 40671,
    "free" : 5142,
    "speculative" : 3793
  }
},
  "largestProcess" : "AppStore",
  "genCounter" : 1,
  "processes" : [
  {
    "uuid" : "7607487f-d2b1-3251-a2a6-562c8c4be18c",
    "states" : [
      "daemon",
      "idle"
    ],
    "age" : 3724485992920,
    "purgeable" : 0,
    "fds" : 25,
    "coalition" : 68,
    "rpages" : 229,
    "priority" : 0,
    "physicalPages" : {
      "internal" : [
        6,
        183
      ]
    },
    "pid" : 350,
    "cpuTime" : 0.066796999999999995,
    "name" : "SBRendererService",
    "lifetimeMax" : 976
  },
.
.
{
  "uuid" : "f71f1e2b-a7ca-332d-bf87-42193c153ef8",
  "states" : [
    "daemon",
    "idle"
  ],
  "lifetimeMax" : 385,
  "killDelta" : 13595,
  "age" : 94337735133,
  "purgeable" : 0,
  "fds" : 50,
  "genCount" : 0,
  "coalition" : 320,
  "rpages" : 382,
  "priority" : 1,
  "reason" : "highwater",
  "physicalPages" : {
    "internal" : [
      327,
      41
    ]
  },
  "pid" : 2527,
  "idleDelta" : 41601646,
  "name" : "wifianalyticsd",
  "cpuTime" : 0.634077
},
.
.

  1. 方法二: 網(wǎng)上資料

ios app maximum memory budget

大致就是60%

Split 工具

  1. 方法三: 主動觸發(fā) didReceiveMemoryWarning

當(dāng)內(nèi)存不夠用時,iOS 會發(fā)出內(nèi)存警告,告知進(jìn)程去清理自己的內(nèi)存, 在當(dāng)前頁面(Controller)中,這個方法是 - (void)didReceiveMemoryWarning??梢酝ㄟ^不停地增加內(nèi)存,來獲取當(dāng)前設(shè)備的 OOM 閾值。

RAM 和 ROM

  • RAM(random access memory)隨機(jī)存儲內(nèi)存,這種存儲器在斷電時將丟失其存儲內(nèi)容,故主要用于存儲短時間使用的程序
  • ROM(Read-Only Memory)只讀內(nèi)存,是一種只能讀出事先所存數(shù)據(jù)的固態(tài)半導(dǎo)體存儲器

App 啟動時,系統(tǒng)會將 App 程序從ROM 中拷貝到內(nèi)存(RAM),然后在RAM 里面執(zhí)行代碼

內(nèi)存分頁

虛擬內(nèi)存以page(頁)為單位進(jìn)行管理(每頁的容量,32位機(jī)器大小為 4 KB 而 64 位機(jī)器大小為 16 KB)

內(nèi)存頁也有分類,一般來說分為 Clean Memory 、 Dirty Memory 和 Compressed Memory

  • Clean Memory
    例如,image.jpg,F(xiàn)rameworks

  • Dirty Memory
    所有堆分配對象(例如 malloc,Array,NSCache,UIViews,String 和 圖像解碼緩沖區(qū),例如CGRasterData,ImageIO 和 Frameworks)都會是 Dirty Memory

  • Compressed Memory
    內(nèi)存壓縮主要執(zhí)行兩個操作

  1. 壓縮未訪問的頁面
  2. 訪問時解壓縮頁面

壓縮內(nèi)存能夠在內(nèi)存緊張時將最近使用的內(nèi)存使用率壓縮到原始大小的一半以下,并在需要時可以解壓縮和重新使用。它不僅節(jié)省了內(nèi)存,而且提高了系統(tǒng)的響應(yīng)速度。

例如,當(dāng)我們使用 NSDictionary 來緩存數(shù)據(jù)時,假設(shè)現(xiàn)在我們已經(jīng)使用了 3 頁內(nèi)存,當(dāng)我們不訪問它時,它可能被壓縮為 1 頁,而當(dāng)我們再次使用它時,它將被解壓縮為 3 頁

內(nèi)存分區(qū)

  • 代碼區(qū):函數(shù)體的二進(jìn)制代碼
  • 常量區(qū):常量字符串,const常量
  • 全局/靜態(tài)區(qū):全局變量和靜態(tài)變量、靜態(tài)全局變量
  • 堆(heap):存OC對象,地址越來越淡,一般由程序員分配釋放
  • 棧(stack):函數(shù)的參數(shù)值,局部變量的值、對象指針地址等

棧區(qū)和堆區(qū)的比較

  • 分配方式不同
    棧是自動分配和釋放,堆是由程序員來分配和釋放

  • 申請大小的限制
    棧區(qū):容量大小一般是 2M,比較小
    堆區(qū):不連續(xù)的,空間比較大

  • 申請效率的比較
    棧:系統(tǒng)自動分配,速度較快,但是不受程序員控制。
    堆:由 alloc 分配的內(nèi)存,速度較慢,并且容易產(chǎn)生內(nèi)存碎片。

ARC

ARC 背后的原理是依賴編譯器的靜態(tài)分析能力,通過在編譯時找出合理的插入引用計數(shù)管理代碼,從而徹底解放程序員。

自動釋放池

自動釋放池就是一個雙向列表

參考資料:
從 OOM 到 iOS 內(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)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 一、 在 Obj-C 中,如何檢測內(nèi)存泄漏?你知道哪些方式? 目前我知道的方式有以下幾種 Memory Leaks...
    長茳閱讀 492評論 0 2
  • 一、說一下懸垂指針、野指針的區(qū)別 垂懸指針指針指向的內(nèi)存已經(jīng)釋放,但是指針還存在,這就是 垂懸指針 或者 迷途指針...
    楓葉無處漂泊閱讀 803評論 0 9
  • 一、在 Obj-C 中,如何檢測內(nèi)存泄漏?你知道哪些方式? 目前我知道的方式有以下幾種 Memory Leaks ...
    maskerII閱讀 478評論 0 0
  • 如果有不好的地方或者不全面的地方請留言批評指正,拜謝~~~ 引發(fā)反思棧怎么清除?會引發(fā)什么狀況?怎么使棧溢出?堆空...
    代碼守望者閱讀 397評論 0 1
  • 1.網(wǎng)絡(luò) 1.網(wǎng)絡(luò)七層協(xié)議有哪些? 物理層:主要功能:傳輸比特流;典型設(shè)備:集線器、中繼器;典型協(xié)議標(biāo)準(zhǔn)和應(yīng)用:V...
    _我和你一樣閱讀 3,917評論 1 38

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