Unity遊戲內存分佈概覽

這是侑虎科技第1100篇文章,感謝作者放牛的星星供稿。歡迎轉發分享,未經作者授權請勿轉載。如果您有任何獨到的見解或者發現也歡迎聯繫我們,一起探討。(QQ羣:793972859)

作者主頁:https://www.zhihu.com/people/niuxingxing

內存是遊戲性能優化中一個非常重要的方面。尤其是移動設備,硬件設備受限,但又需要對該類機型的用戶進行覆蓋兼容的時候。遊戲是在PC或者Mac下開發的,但是最終卻是(大部分)在移動端(只考慮安卓iOS)運行的,如果在內存方面沒有控制好,那麼很容易會因爲OOM的原因被移動端的OS殺掉進程。

但實際上,在不同的操作系統之下,內存的管理策略差距較大。針對各個平臺都有比較專業的內存分析工具,但這些工具由於平臺不一樣,統計策略不一樣,甚至是系統版本不一樣都會導致統計偏差。比如,XCode的Memory Report頁面和它自己在Instrument下統計的數據就不一樣。

還有一些wrap的包裝損耗,比如一張Texture,它的大部分Data內存會進入Native中,但仍然需要對外包裝一些Class供邏輯層調用,這部分又會進入Mono堆中。又比如,System Framework的一部分Const數據會進入Clean內存,而另一部分會進入Dirty,又可能會被系統進一步壓縮爲Swap內存。

Unity視角下的內存分析,更多的是注重Unity本身所管理的內存(實際上不是它分配的它也管理不到),但一個Unity遊戲實際上是要跑在平臺上的(比如安卓或者iOS)。那麼除了Unity自身分配的內存之外,還有一部分則來自於系統的共享庫。再者,一個複雜的Unity遊戲往往會引用很多第三方的插件,而這些插件所分配的本機內存也是Unity所無法顧及的。

1.1 Unity的內存起源

Unity實際上可以看作是一個使用C++開發的遊戲引擎,它使用.net的腳本虛擬機。Unity從虛擬內存中給原生(C++)對象和虛擬機分配內存,同樣,第三方插件的內存分配也都是在虛擬內存池中。

原生內存(Native Memory )是虛擬內存的一部分,它用來給所有需要的對象分配內存頁面,包括Mono堆(Mono Heap)。

如上所說,Mono堆是出於虛擬機的需要而專門分配的本機內存的一部分。它包含了所有由C#分配的託管的內存類型,而這些內存的託管對象就是垃圾收集器(Garbage Collector),簡稱GC。

Unity內部有幾個專門的分配器,它們負責管理虛擬內存的短期和長期分配需求。所有的Unity資產(Assets)都是存儲在原生內存中的。但這些資產會被輕量的包裝成Class,以供邏輯訪問和調用。也就是說,如果我們用C#創建了一張Texture,那麼它的大部分原始數據(RawData)存在於Native內存中,並且會有很小的一個Class對象進入到虛擬機中,也就是Mono堆中。

1.2 Reserved/Used

內存分頁(iOS上大部分爲16K/page)是內存管理的最小單位,當Unity需要申請內存的時候,都會以block的方式(若干個頁)進行申請。如果某一頁在若干次GC(iOS爲8次)之後仍然爲Empty,它就會被釋放,實際的物理內存就會被還給物理內存。

但由於Unity是在虛擬內存中管理內存的,因此虛擬空間的地址並不會返還,所以它就變成“Reserved”的了。也就是說這些Reserved地址空間不能再被Unity重複分配了。

所以在一個32位的操作系統上,如果虛擬內存地址發生頻繁分配和釋放就會導致地址空間耗盡,從而被系統殺掉。

1.3 GC與內存碎片

Mono堆申請的物理內存是連續的,並且Mono堆在向操作系統申請擴展內存的時候,非常耗時。所以大部分情況下,Mono堆都會盡量保持自己已經申請到的物理內存,以防止之後要用。所以除了虛擬空間地址之外,Mono堆申請的內存也存在Reserved概念。

由於內存的分配單位是頁,也就是說如果一個頁只存儲了一個int值,那麼該頁仍然會被表示爲Used,它們的物理內存不會被釋放。當然如果某個內存大於一頁,就會申請多個連續頁。

如果某個時刻,堆內存被GC了,那麼這部分的物理地址就會被空出來。

當下一次需要申請堆內存的時候,Mono堆會首先檢查當前堆內的空間內是否存在連續的空間能容納這次內存申請,如果不夠就會進行一次GC,也就是我們最討厭那個GC操作。之後,如果還是找不到這樣的block,Mono堆就會執行內存擴展操作,向操作系統要更多的內存。

而這些空出來,卻又不能被重複利用的內存就會成爲內存碎片。它們既不能被利用,又不會被銷燬。

比如上圖,Mono Reserved/Used的關係:

Reserved size:256KB + 256KB + 128KB = 640KB

Used:88 562B

1.4 Profiler Simple視圖

使用Unity的Profiler進行內存分析的時候,在Simple模式下,可以看到類似如下截圖:

這裡展示的是Unity自己所管理的虛擬內存。

這就很明顯了,第一行展示的是Used內存,第二行展示的Reserved。

Total:

Unity:所有Unity申請和管理的內存減掉、和。也就是說包含Mono Heap。

Mono:託管堆內存。

GfxDriver:

GPU顯存開銷,主要由Texture、Vertex buffer以及index buffer組成。

但不包括Render Targets。

(也不包含其他平臺的驅動層)

FMOD:

由FMOD申請的內存。

Video:

視頻文件播放所需的內存。

Profiler:分析器自身開銷。

這裡的Total Reserved也還不是遊戲虛擬內存的精確值,因爲:

它不包括遊戲的二進制可執行文件,已加載的libraries和frameworks的大小。

GfxDriver值不包括渲染目標和由驅動層分配的各種緩衝區。

分析器只看到由Unity代碼完成的分配,看不到第三方native插件和操作系統的分配。

1.5 Profiler Detailed 視圖

Detailed視圖樣例如下:

它展示了虛擬內存的詳細分配情況。

Assets— 當前從scenes、Resources和Asset Bundles加載的總資源。

Built-in Resources— Unity Editor資源或者Unity default資源。

Not Saved— 被標記爲DontSave的GameObjects。

Scene Memory— GameObject和它附屬的Components。

Other— 其他不在上面幾條分類中的。

大部分時候,內存中的熱點問題都可以在Assets中找到。比如通過引用次數找出紋理和資源的泄漏(一般泄漏的資源沒有引用次數)。

這裡我要關注一下Other目錄下的Objects項。

實際上這裡值是由一些BUG導致的。這一項表示各種從Object繼承的對象,包括紋理,Mesh等等。它們在某個時刻和實際上的對象斷開了鏈接,可以忽略。

System.ExecutableAndDlls:這是Unity的猜測值。

它嘗試通過彙總文件大小來猜測已加載的二進制代碼消耗的內存。

ShaderLab:這些是與編譯着色器有關的分配。

着色器本身具有自己的object root,並在Shaders下列出。

1.6 Unity視角的侷限

Unity的內存分析遠不止自帶的Profiler這一項。我們常用的還有:

MemoryProfiler

MemoryProfiler Extension

但它們都有一個同樣的問題,就是依賴Unity自身提供的Profiler API。換句話說,儘管各個工具在數據展示和操作方式上有不同,但它們測量的結果沒什麼不同。

也就是說Unity視角下的工具都只看到由Unity代碼完成的分配,看不到第三方native插件和操作系統的分配。

但一個完整的Unity項目最終是要跑在平臺上的,那麼它就會和平臺的內存分析工具統計的結果有較大的誤差。另外也很難確切掌握Unity項目真正的內存分佈和開銷。

這一篇,我們介紹Unity遊戲在iOS平臺下的內存情況。

當Unity自身的工具無法滿足內存分佈的全景統計時,我們轉頭看向擁有最好調試能力的XCode工具。一般我們需要將Unity項目導出成XCode工程,然後使用XCode以及它的Instrument進行Profiler。

2.1 iOS視角下的內存管理

還是從起源講起。iOS視角下的內存和Unity視角下的已經完全不一樣了。無論是概念,管理或者類別上。

作爲操作系統,iOS關注的層面不會再像Unity那麼細緻(實際上它也做不到),它更多的是關心操作系統層級的內存管理,以及對沙盒APP的各種底層的內存操作記錄,但這也只能從堆棧上反映。實際上,它記錄的是APP跟操作系統申請內存的記錄。

Unity的項目作爲一個APP運行在iOS平臺上,那麼它只會被iOS系統當做一個是普通的APP進行記錄和分析。而事實上,Unity相比於原生的APP而言,還有天然的劣勢。相比於iOS原生APP和控件而言,Unity申請內存的用途它是完全不知道的。或者說操作系統根本就不會關注APP申請完的內存怎麼用。

這就好比家長(iOS)給孩子(Unity)零用錢,家長會記錄你前天跟我要了100塊說要買試卷(堆棧記錄),昨天又要了50塊交班費,今天又要了100塊去和同學吃飯。當孩子要的數量太多,超過了家長的忍受限度之後,就會被終結(你這個月的零花錢沒有了!)。

而孩子的視角則不一樣,我前天要了100塊錢,20塊錢買了語文,20塊錢買了數學,20塊錢買了英語,來回坐車花了8塊,還剩32,算了,不還回去了,萬一過幾天物理或者化學還要買呢?

2.2 iOS使用的內存類型

和Unity只關注虛擬內存不一樣,OS需要關注物理內存Physical Memory(RAM)。尤其是移動平臺,更是要將有限的內存使用到極致纔可以。所以不少PC平臺使用的內存策略移動平臺並不能用,比如交換空間(iOS只能對Clean類型的內存做Paging )。

接下來就列舉一下iOS系統所使用的內存形態:

Physical Memory:iOS設備上的物理芯片內存。也就是我們常說的機器內存。移動設備上,物理內存的實際用量要扣除操作系統自身的佔用量。iOS內存崩潰閾值 這篇文章裡記錄了iOS各種設備上APP所能使用的物理內存量。

Virtual Memory(VM):虛擬內存,也是OS給每一個APP分配的虛擬地址空間,這和Unity的虛擬空間地址比較類似。它由內存管理單元MMU( memory management unit),進行管理,並和實際的物理內存進行映射。前面也說過了,內存是按頁分配的,早期的iOS處理器使用的是4K/頁(也個別代是用64K的),A9之後統一使用16K/頁。虛擬內存一般由代碼段、動態庫、GPU驅動,Malloc堆和一些其他的部分所組成。

GPU Driver Memory:iOS系統使用所謂的統一架構,即GPU和CPU共享某一部分內存,比如紋理和網格數據,這些是由驅動器進行分配的。另外還有一些是GPU的驅動層以及GPU獨享的內存類型(Video memory )。

Malloc Heap:APP實際申請內存的地方。Unity的內存申請行爲都會發生在這裡,通malloc和calloc函數進行內存申請。蘋果沒有公開可使用的最大虛擬堆的地址,理論上來說它只受指針大小的限制(32位或者64位),但實際的經驗來看,遠低於理論值,並且沒有規律。實際經驗總結如下:

和Unity自身的虛擬地址一樣,應用程序最好不要頻繁的申請和釋放內存。

Resident Memory:駐留內存,這是遊戲或者App實際所佔用的物理內存。一般來說,當應用向系統申請內存的時候,虛擬內存是直接增長的。但如果申請完的內存並沒有向裡面寫入數據,它並不會產生實際的物理內存分配。所以虛擬內存是>=駐留內存的。

Clean Memory:Clean內存是駐留內存的一部分。但這部分內存的類型是隻讀的。常見的Clean內存包括System frameworks的常量部分、應用程序的二進制文件、內存映射文件等。由於是隻讀的特性,因此它可以在應用程序內存不足的時候被Page Out。

Dirty Memory:與Clean相對的就是Dirty內存。這部分是指無法被OS換頁操作的。

Swapped Compressed Memory:Swapped Compressed實際上屬於Dirty內存。當應用內存不足的時候,OS會將髒內存中使用頻次較少的內存進行壓縮存放。等需要使用的時候再重新解壓出來。這些算法和策略目前是沒有被公開出來的,但從經驗來看,OS會比較積極的來做壓縮以減少髒內存的總量。注意,這裡的壓縮交換並不是傳統操作系統上的磁盤空間交換。

上圖展示的就是壓縮的過程。壓縮和解壓都是由CPU損耗的。

2.3 FootPrint

Footprint是蘋果推薦的內存度量及優化的指標。當Memory Footprint的值達到Limit line時,就會觸發內存警告,並進一步導致OOM。

Footprint主要是由Dirty和Compressed組成。或者說Resident是由Footprint和Clean組成的。Footprint沒有統一標準,它會因爲設備、操作系統、甚至是當前運行環境不同而不同。

字節現有的測試工具GamePref在測試iOS的時候,抓取的就是Footprint內存。詳細數據可以參考GamePerf 使用說明文檔 。

2.4 Xcode memory gauge

這是XCode調試時最簡單的界面。切換至Debug頁簽下就能看到。

綠色表示內存良好,黃色是危險區域,如果不及時處理馬上就會被OS殺掉。

儀表的最大內存指的是設備的物理內存,但實際上,它沒有在正式的場合說明指針內存指的是什麼內存。但從測試的結果上來說,它總是比用VM Tracker工具測出的Dirty Memory + Swapped Memory要大10-15MB。

但實際上,這個數值並不是OS殺掉APP的唯一判斷標準。一般在殺掉一個APP的時候會經過以下的步驟:

嘗試移除Clean內存頁。

如果某個APP佔用了太多的Dirty內存,OS發送一個內存警告過去,讓它釋放資源。

幾次警告之後,如果Dirty內存仍然佔用很高就會被Kill掉。

由於iOS殺APP的策略也是不透明的,所以如果要防止APP被系統終結,唯一的辦法就是儘量降低Dirty內存。

2.5 VM Tracker

VM Tracker是XCode Instruments工具組裡的一個。它提供比較詳細的虛擬內存的分佈情況,也是爲一個提供Dirty Memory信息的工具。但遺憾的是它沒有展示內存分配的目的和時間。

一張典型的VM Tracker的Profiler快照如下:

表頭分別是:

Type— 內存類型

Resident Size— Resident Memory內存

Dirty Size— Dirty Memory內存

Swapped Size— Compressed Swapped Memory內存

Virtual Size— Virtual Memory內存

Res. %— Resident Memory和Virtual Memory的佔比

接下來就是一些實際的類型在各個層面的具體數值了。就不一一介紹類型了,挑幾個重點解釋一下。

*All*— 所有的分配

*Dirty*— Dirty Memory

IOKit— graphics driver memory,比如:render targets, textures, meshes, compiled shaders等

VM_ALLOCATE— 主要是Mono Heap。如果此項值過大,可以用Unity Profiler來查看具體分配

MALLOC_*— 主要是Unity native或者是third-party plugins分配的內存

__TEXT— 只讀的可執行代碼段和靜態數據

__DATA— 可寫的可執行code/data

__LINKEDIT— 各種鏈接庫的實際元數據,比如:符號、string、重分配的表格等

可以通過比對虛擬內存和髒內存等各個組的信息,可以做一些內存根源的分析。比如上面這個快照,如果分析可以得出什麼結論呢?

2.5.1 Regions Map

Regions Map是VM Tracker的另外一個視角,主要提供了對內存分頁的展示和虛擬地址空間的結構。

比如上圖就可以看出,Mono Heap塊的內存並不是連續的。

2.6 Allocations

Allocations工具顯示應用程序地址空間中的所有分配,這些分配是由所有線程(包括Unity本機代碼,垃圾收集器,IL2CPP虛擬機,第三方插件等)在所有線程上完成的。

相比於VM Tracker而言,Allocations的優勢就在於它能查看任何時間段內的內存分配情況。通過堆棧還可以看到是由哪段代碼完成的分配。

但它仍然是有缺陷的,它只能查看分配情況,而無法查看駐留情況。

在CallTrees的視角下我們可以看到堆棧情況。

比如上面這個截圖就可以看出,代碼創建了一個Pooler類的實例,該實例克隆了一些Prefab,從而導致了配內存。

在Summary下則可以看到虛擬機內存分配的詳細列表。

當選中一個點開之後,能看到更詳細的分配情況。比如下面這個內存分配就是Json的解析所導致的。

2.7 Memory Debugger

除了工具分析之外,Xcode還提供了Memory Debugger功能。它需要在工程設置裡開Malloc Stack。

之後點擊Debug頁簽下的這個標識,就能抓取內存幀進行分析了。

這裡可以查看每個字節的分配情況。但它又太過於細節了。

2.8 vmmap

通過對Memory快照進行導出操作,我們還可以使用命令行工具對內存進行更細緻的分析。

導出的memgraph文件通過vmmap的命令行可以物理內存的實際分配情況。

當然vmmap工具還有更多的命令行可以支持查看更多的細節和內容,感興趣可以查看嘉棟大佬寫的《寫給Unity開發者的iOS內存調試指南》的後半部分。https://zhuanlan.zhihu.com/p/87310853

如果你想分析內存泄漏的情況,也可以使用leaks App.memgraph命令。比如下面這個循環引用:

2.9 XCode視角的侷限

XCode視角一樣有它自己的侷限性。Unity關注或者管理的重點是虛擬內存。而XCode從OS的視角關注的更多的物理內存。

Unity是無法統計系統庫和三方插件,XCode可以統計,但是很難區分。因爲對於OS而言,這些內存都是APP跟我要的,都是Malloc Heap申請,所以都統歸在一個類別中。如果真的要進行庫區分,那麼我們需要人工的將所有的函數分配收集起來,然後人工分類出哪些是Unity的,哪些是APlugin的,哪些是Lua分配的,等等。

再一個是XCode的分析工具確實很多,但這些工具統計各自統計的維度相加還是有彼此不相同的問題。也就是說即使是XCode自己的工具,它也沒有把標準完全統一起來。

最後,XCode的工具鏈所統計的內容過於底層和細節,我們可以使用它很快速的定位到內存異常,但卻很難將所有的內存進行快捷分類。

iOS操作系統是基於Unix的,Android操作系統是基於Linux的,而Linux又是基於Unix的,所以安卓系統在內核上和iOS非常相似。

所以,安卓的內存管理策略和iOS也非常相似。但不同的是iOS系統是封閉的,並且每一代產品的硬件是已知的,甚至是可以枚舉的。而安卓系統由於開源,硬件五花八門,非常難以控制,但開源的系統也讓它有更多的可能性。

3.1 安卓視角下的內存管理

雖然內存策略相似,但在名詞和實際管理過程中還是略有差異。比如安卓就將內存分爲三個類型:

RAM:也就是我們常說的內存,但其大小通常有限。高端設備通常具有較大的RAM容量。

zRAM:是用於交換空間的RAM分區。當內存不足的時候,OS會將RAM中一部分數據進行壓縮,然後存至zRAM。設備製造商可以設置zRAM大小上限。

Storage:通常說的存儲器。平時的APP,照片,緩存文件啥的都在這裡。

但與iOS的Footprint不同的是,安卓的內存是另外一種叫法。

VSS- Virtual Set Size虛擬耗用內存(包含共享庫佔用的內存)

RSS- Resident Set Size實際使用物理內存(包含共享庫佔用的內存)

PSS- Proportional Set Size實際使用的物理內存(比例分配共享庫佔用的內存)

USS- Unique Set Size進程獨自佔用的物理內存(不包含共享庫佔用的內存)

一般來說內存佔用大小有如下規律:VSS >= RSS >= PSS >= USS。

目前Unity的遊戲在安卓上的指標默認都在使用PSS。這是什麼意思呢?

比如我們有一段內存頁如下:

其中有一個位置共享服務,Google Play和某個遊戲應用都在使用。這就很難界定到底是哪個APP用的更多,如果我們把位置共享服務的所有內存都分別加給兩個應用,那麼計算視角就是RSS。這確實是比較準確的物理內存使用,但這樣一來位置共享服務就計算了兩次,三個應用那就是三次。顯然是不對的。

於是,乾脆一點,大家平分這個共享服務內存。那麼這個計算視角就是PSS,雖然不完全合理,但是是目前最平衡的方案了。

3.2 LMK (Low Memory Killer)

iOS中,Footprint到達臨界值就會被OS殺掉了,安卓也是一樣。不過相比於iOS來說,安卓的LMK進程更加的透明。

LMK使用一個名爲oom_adj_score的“內存不足”分來確定正在運行的進程的優先級,並以此決定要終止的進程。最高分的進程最先被終止。後臺應用最先被終止,系統進程最後被終止。下表列出了從高到低的LMK評分類別。評分最高的類別,即第一行中的項目將最先被終止。

以下是上表中各種類別的說明:

後臺應用:之前運行過且當前不處於活動狀態的應用。LMK將首先從具有最高oom_adj_score的應用開始終止後臺應用。

上一個應用:最近用過的後臺應用。上一個應用比後臺應用具有更高的優先級(得分更低),因爲相比某個後臺應用,用戶更有可能切換到上一個應用。

主屏幕應用:啓動器應用。終止該應用會使壁紙消失。

服務:由應用啓動的服務,可能包括同步或上傳到雲端。

可覺察的應用:用戶可通過某種方式察覺到的非前臺應用,例如運行一個顯示小界面的搜索進程或聽音樂。

前臺應用:當前正在使用的應用。終止前臺應用看起來就像是應用崩潰了,可能會向用戶提示設備出了問題。

持久性(服務):這些是設備的核心服務,例如電話和WLAN。

系統:系統進程。這些進程被終止後,手機可能會重新啓動。

原生:系統使用的極低級別的進程(例如:kswapd)。

設備製造商可以更改LMK的行爲。

3.3 Android Profiler

由於安卓平臺構建Unity包的便捷性,以及安卓本身的Studio對內存和性能分析工具的不足,導致很多時候我們都選擇在安卓連接Unity Profiler進行內存調試。

但實際上,安卓現在也有不少工具是可以剖析性能了。比如:

https://developer.android.com/studio/profile

由於大多數情況下還是在使用XCode進行分析,所以這部分目前沒有實踐。未來會找時間深入調研工具的用法和技巧。

另外,谷歌大會上,安卓的開發人員推薦使用最新的性能分析工具Perfetto 。

https://perfetto.dev/docs/quickstart/android-tracing

就目前而言,安卓的工具和XCode一樣,無法鑑別應用程序中的內存分配是由Unity完成的還是由Plugin完成的。所以他們給的建議也是隔離測量。

鑑於以上的調研結果,思考了兩個解決方式。

4.1 逐個擊破

從Unity的視角出發。既然Unity無法統計三方插件的消耗,那我們就用“差異法”來逐個擊破每個用到的三方插件。

比如我們基於一個mini工程,首先測量出該mini工程當前各內存指標值。然後接入三方插件,使用用例再次測試相同指標。得出的差異值就約等於該插件的內存消耗。

當我們將所有的插件的指標都按照該方式測量之後,再加上Unity自身的Reserved內存就可以看做是當前的內存分佈情況。

該方法自然有其弊端:

差異值會受到不同設備當前環境的共享庫影響,可能造成Plugin在在不同機器差異較大。

方法並非白盒,無法確定實際會受到哪些客觀條件或者內部邏輯的影響。

由於移動平臺的髒內存策略,實際上只能測量出虛擬內存的差異值。詳情看如下測試結論:不同內存分配方式對實際內存的影響。

很難做到控制變量。所以需要針對插件寫覆蓋面足夠全的測試用例。

4.2 海底撈月

另外一招就是海底撈月。從最底層的Malloc出發,寫Hook函數監聽內存申請,最後彙總分析。比如這篇文章:《mallochook內存分配回調(glibc-3-內存)》。

https://www.jianshu.com/p/2beb412bd97b

這實際上和移動平臺的內存工具模式相同。只不過用自定義的方式我們可以更加定製化工具的顯示規則。雖然方案可行,但它存在和移動平臺工具相同的問題,統計出的堆棧應該如何進行歸類。怎麼判定哪個函數歸屬於引擎,哪個歸屬於三方插件,以及系統共享庫或者Framework呢?

補充:

對安卓內存的補充:《Unity如何統計安卓PSS內存?》

https://zhuanlan.zhihu.com/p/372883142

《[教程彙總+持續更新]Unity從入門到入墳——收藏這一篇就夠了。》

https://zhuanlan.zhihu.com/p/151238164

5. 參考

[1]Fixing Performance Problems - 2019.3 - Unity Learn

https://learn.unity.com/tutorial/fixing-performance-problems-2019-3

[2]Understanding iOS Memory

https://docs.google.com/document/d/1J5wbf0Q2KWEoerfFzVcwXk09dV_l1AXJHREIKUjnh3c/edit

[3]寫給Unity開發者的iOS內存調試指南

https://zhuanlan.zhihu.com/p/87310853

[4]Finding iOS memory | Rambling Llamas

https://liam.flookes.com/wp/2012/05/03/finding-ios-memory/

[5]Unity - Manual: Understanding the managed heap

https://docs.unity3d.com/Manual/performance-managed-memory.html

[6]Memory Management in Unity - Unity Learn

https://learn.unity.com/tutorial/memory-management-in-unity

[7]iOS Memory Deep Dive - WWDC 2018 - Videos - Apple Developer

https://developer.apple.com/videos/play/wwdc2018/416/

[8]Delivering Optimized Metal Apps and Games - WWDC 2019 - Videos - Apple Developer

https://developer.apple.com/videos/play/wwdc2019/606/

[9]Gain insights into your Metal app with Xcode 12 - WWDC 2020 - Videos - Apple Developer

https://developer.apple.com/videos/play/wwdc2020/10605/

[10]安卓進程間的內存分配

https://developer.android.com/topic/performance/memory-management

[11]Memory allocation among processes

https://developer.android.com/topic/performance/memory-management#types_of_memory

[12]Understanding Android memory usage

https://www.youtube.com/watch?v=w7K0jio8afM

[13]Android memory and games

https://www.youtube.com/watch?v=Do7oYWwOXTk&t=314s

文末,再次感謝放牛的星星的分享,作者主頁:https://www.zhihu.com/people/niuxingxing,如果您有任何獨到的見解或者發現也歡迎聯繫我們,一起探討。(QQ羣:793972859)