AssetBundle和热更新和语言基础
移動端設備(iOS/Android)對單個 App 的總內存有限制。
1.Native 堆(贴图,模型,文件)
不受 GC 管辖: GC 扫描不到这里,所以即使你 C# 里的引用断了,如果 Native 层没卸载,内存依然被占用(这就是 Native 内存泄漏)。Native 堆會觸發 GC 嗎?
不會。 Native 堆(紋理、網格)的內存是由 Unity 引擎底層(C++)手動管理的。如果 Native 內存不夠,系統會直接閃退(OOM),或者觸發引擎內部的 UnloadUnusedAssets(但這不是 GC,這是資源卸載)。
Native 堆:按需申請,用完即還,主动销毁会还给作业系统,没有预分配,Native 堆效率高,能直接给GPU, 當你調用 material.SetTexture("_MainTex", myTex) 時,你只是在 C# 層傳遞了一個引用的 Wrapper(包裝對象)。Unity 底層(C++)會立刻找到這個 Wrapper 指向的 Native 堆 裡的真實二進制數據,然後把 Native 地址交給 GPU。 GPU 無法直接使用託管堆對象,
2.托管堆
託管堆 C# 分配對象的地方不够的时候,Mono 或 IL2CPP 的內存管理器會去託管堆找一塊連續的空地。如果剩餘的連續空間不足以放下這個新對象,就會觸發 GC。GC 會掃描所有 C# 對象,看看誰沒人要了(沒有引用了),然後把它標記為「可回收」,釋放空間。如果 GC 完之後,空間還是不夠放新對象,託管堆會向操作系統申請擴張。 託管堆一旦擴張,極難縮回(它會一直佔著內存不還給系統)。這就是為什麼頻繁 new 對象會導致遊戲佔用內存越來越大的原因。託管堆的持續申請會造成閃退嗎,会,使用内存超过就会闪退
3.AB包和资源(Texture,网格)的关系
關係:AB 包是「母親」,資源是「孩子」。
Unload(false):相當於「斷絕母子關係」。AB 包(母親)從內存消失了,但資源(孩子)還留在 Native 堆。
冗餘產生過程:
你加載了 AB 包 Character,加載了裡面的 TextureA。
你調用了 Unload(false)。這時 AB 包沒了,但 TextureA 還在 Native 堆。
你再次加載 AB 包 Character,然後又加載 TextureA。
重點來了:因為之前的母子關係斷了,Unity 不知道內存裡已經有一個 TextureA 了,它會重新在 Native 堆再創建一個全新的 TextureA。
AB 包的引用計數:到底是算「包」還是算「資源」?
引用計數通常是針對「AB 包」。因為 AB 包是內存管理的最小單位。只要包裡還有一個資源(比如一張小貼圖)被引用,整個 AB 包的鏡像(SerializedFile)就必須留在 Native 堆。你無法卸載半個 AB 包。
託管堆的包裝對象算一個引用嗎? 算
4.LoadFromFile vs LoadFromMemory
LoadFromFile (文件句柄):Unity 只是在硬盤上「打開了書」,並沒有讀內容。內存裡只佔一個很小的索引(文件句柄)。只有當你 LoadAsset 時,才從硬盤讀對應那塊資源。極省內存。
LoadFromMemory:你先把 AB 包的所有二進制數據讀進 C# 的 byte[] 數組(這在託管堆佔一份內存),然後 Unity 把它拷貝到 Native 堆(又佔一份內存)。內存直接翻倍,極其浪費。
為什麼還要 LoadFromMemory? 用於加密。如果你不想讓別人在硬盤看到 AB 包內容,你會在內存中解密後再加載。
LoadFromMemory 還有用嗎? 基本沒用了。現在只有那種「動態從網絡下載字節流且不存硬碟」的極端安全場景才會用。18k 方案首選偏移量加密,因為它省內存且快。
LoadFromFile 随机访问,跳转到指定偏移量后随机
切換場景 Unity 會自動調用 Resources.UnloadUnusedAssets 嗎?
不會主動調用。
Unity 切換場景(LoadScene)只會自動銷毀當前場景 Hierarchy 裡的 GameObject(託管堆實例)。
那些由 AssetBundle.LoadAsset 加載出來的 Native 資源(紋理、網格),如果沒有主動卸載,會一直殘留在 Native 堆。你必須在切場景時手動調用 Resources.UnloadUnusedAssets(),或者更專業的做法是:使用自己寫的 引用計數系統,在切場景時對沒人用的 AB 包執行
Resources.UnloadUnusedAssets() 會卸載什麼?
會卸載實例化的東西嗎? 不會。
它只會卸載 Native 堆 裡的資源(Asset),且前提是:這個資源在 託管堆(C#) 裡已經沒有任何包裝對象(Wrapper)引用它了。
例子:如果你 Destroy(gameObject) 了,但沒調用 UnloadUnusedAssets,貼圖還在 Native 堆。調用了,它發現沒人用了,就會把貼圖從 Native 堆幹掉。
5.ab包的引用计数
计数代表这个ab包的load次数,load出来一次计数就是1,不被其他ab包依赖的ab包的计数就是1,被其他ab包的依赖的ab包的计数是有多少个依赖他的ab包load了,现在还在它就有多少个计数
如果你的代碼裡主動寫了 ABManager.Load("Texture_C"),計數會變成多少?
答案:3。
組成:UI_AB (1) + UI_BC (1) + 你手動加載 (1) = 3。
結論:你必須手動調用一次 Unload("Texture_C"),否則即便兩個 UI 都關了,Texture_C 的計數還是 1,它會永久殘留在內存裡(內存洩漏)。
循環引用:Unload(true) 能破局嗎?
場景:A 依賴 B,B 依賴 A。
答案:可以打破,但有順序風險。
如果你主動對 A 調用 Unload(true):
A 包的鏡像和 A 裡的所有資源會強制銷毀。
此時,A 對 B 的引用計數會 -1。
如果 B 此時計數歸 0,B 也會跟著銷毀。
6.手绘AB包加载内存流向图

实例化n份贴图,会造成n个托管堆的内存增加和n个native堆的内存增加