1盯串、Unity內(nèi)存按照分配方式分為:
(1)Native Memory(本地內(nèi)存)
(2)Managed Memory(托管內(nèi)存)
Editor和Runtime內(nèi)存管理是不同的,Editor下打開(kāi)工程就會(huì)將所有資源加載進(jìn)內(nèi)存,而Runtime是我們調(diào)用相應(yīng)的Load接口時(shí)才會(huì)加載進(jìn)內(nèi)存桥嗤。
2咨堤、Unity按照管理者分為:
(1)引擎管理內(nèi)存
(2)用戶管理內(nèi)存
3、Unity Native Memory管理
Unity通過(guò)重載C++的new/malloc內(nèi)存分配操作符實(shí)現(xiàn)自己的內(nèi)存管理顿涣。
3.1? Allocator與memory label
memory label區(qū)分內(nèi)存類(lèi)型波闹,每一種內(nèi)存類(lèi)型都會(huì)對(duì)應(yīng)一個(gè)Allocator,每個(gè)Allocator有各自的分配策略,每一個(gè)Allocator會(huì)跟蹤一個(gè)內(nèi)存池涛碑。當(dāng)需要申請(qǐng)內(nèi)存時(shí)精堕,就會(huì)找到對(duì)應(yīng)類(lèi)型的Allocator進(jìn)行分配。
3.2? NewAsRoot
調(diào)用NewAsRoot時(shí)會(huì)分配所有
3.3 Stack Allocator
Stack Allocator用于分配局部?jī)?nèi)存蒲障,Stack Allocator的特點(diǎn)就是快歹篓、小、臨時(shí)揉阎。
當(dāng)Stack Allocator分配內(nèi)存時(shí)庄撮,一次分配一個(gè)大的Heap Block塊,然后通過(guò)棧的方式管理這塊Block的分配毙籽。實(shí)際分配內(nèi)存時(shí)會(huì)包含一個(gè)Header和User Data洞斯,Header記錄當(dāng)前使用狀態(tài)、User Data大小等信息坑赡。使用一個(gè)棧頂指針管理內(nèi)存分配巡扇,每次分配只在棧頂頂端新分配一塊內(nèi)存,然后修改棧頂指針垮衷√瑁回收時(shí)如果回收的是非棧頂內(nèi)存只需要修改Header中使用狀態(tài)為Deleted;如果回收的是棧頂指針指向的內(nèi)存搀突,修改標(biāo)記狀態(tài)同時(shí)刀闷,棧頂指針向上回彈,回彈后會(huì)查看當(dāng)前指向的內(nèi)存是否已標(biāo)記為Deleted,如果是則繼續(xù)回彈仰迁,直到棧頂指針指向一個(gè)未標(biāo)記為Deleted的內(nèi)存甸昏。
當(dāng)一個(gè)Block塊無(wú)法分配后,會(huì)再申請(qǐng)一塊Block徐许。但不可能無(wú)限分配施蜜,Stack Allocator有大小限制;
Editor:16MB Main Thread,256KB workers雌隅;Runtime:1MB Main thread翻默,64KB workers
如果我們需求內(nèi)存過(guò)大導(dǎo)致Stack Allocator大小不夠缸沃,Unity會(huì)執(zhí)行Fallback機(jī)制,會(huì)使用Dynamic Heap Allocator分配內(nèi)存修械。Dynamic Heap Allocator分配策略相對(duì)Stack Allocator復(fù)雜很多趾牧,所以耗時(shí)比較大,出現(xiàn)這種情況可能導(dǎo)致卡頓肯污。出現(xiàn)這種情況時(shí)Unity會(huì)給出一個(gè)MemoryManager.FallbackAllocation的提示翘单。所以可以分幀加載一個(gè)比較大或多的資源。
3.4 影響Native內(nèi)存大小因素
(1)Scene:場(chǎng)景中GameObject越多占用內(nèi)存越大
(2)Audio
a蹦渣、DSP Buffer Size(PlayerSettings->Audio)
聲音數(shù)據(jù)緩沖哄芜,太大可能導(dǎo)致聲音延時(shí),太小導(dǎo)致發(fā)送給CPU太頻繁
b柬唯、Force to mono:強(qiáng)制改成單聲道忠烛,內(nèi)存相對(duì)雙聲道減半
c、Format
d权逗、Compression Format
(3)Code Size
模板泛型濫用
(4)AssetBundle
a美尸、TypeTree:保證不同版本序列化正確
如果確保Bundle版本和Unity版本一致,打Bundle時(shí)可以通過(guò)設(shè)置BuildAssetBundleOptions.DisableWriteTypeTree關(guān)閉TypeTree斟薇,減小包體师坎、省內(nèi)存、并且可以提升打包速度堪滨。
b胯陋、LZ4(trunk based)/LZMA
c、設(shè)置合適的AB大小和數(shù)量
(5)Resources文件夾
打包時(shí)會(huì)生成R-B Tree用于Resources檢索
(6)Texture
a袱箱、upload buffer(Project Settings->Quality->Async Upload Buffer Size)
b遏乔、read/write
c、Mip Maps
(5)Mesh
a发笔、read/write
b盟萨、compression
(6)Assets
4、Unity Managed Memory
4.1?
Unity使用Boehm回收器了讨,Boehm是保守式回收器捻激。
由于分代式垃圾回收移動(dòng)內(nèi)存、壓縮等開(kāi)銷(xiāo)較大前计,不適合移動(dòng)平臺(tái)胞谭,所以Unity選擇使用Boehm回收。
Unity不論使用Mono還是IL2CPP都使用Boehm回收器男杈,
Unity嵌入早期版本的Mono(具體原理可參考Mono自動(dòng)內(nèi)存管理分析)丈屹,采用非分代、非壓縮的保守式GC伶棒,托管內(nèi)存只增不減旺垒。
IL2CPP由Unity自己重寫(xiě)垃圾回收機(jī)制彩库,是升級(jí)版的Boehm。使用IL2CPP袖牙,托管內(nèi)存可以降低,內(nèi)存返還的條件就是當(dāng)GC6次舅锄,某一個(gè)Block都是閑置時(shí)鞭达,就會(huì)將這個(gè)Block返還給系統(tǒng)。
4.2 Managed Memory最佳實(shí)踐
(1)使用Destroy皇忿,別用Null畴蹭;
(2)Class VS Struct;
(3)緩存池鳍烁;
(4)Closures and anonymous methods(閉包和匿名函數(shù))叨襟;
(5)Coroutines(協(xié)程);
(6)Configuration(配置表)幔荒;
(7)Singleton
(8)先分配大內(nèi)存再分配小內(nèi)存減少碎片化
4.3?Incremental GC
Unity2019.1引入Incremental GC(漸進(jìn)式GC)糊闽,在使用Boehm回收器的基礎(chǔ)上以增量模式運(yùn)行,將垃圾收集拆分到多幀進(jìn)行爹梁。
大多數(shù)情況下右犹,Incremental GC可以減少垃圾收集尖峰問(wèn)題。但某些情況下姚垃,使用Incremental GC可能會(huì)產(chǎn)生其它問(wèn)題:
(1)Incremental GC是拆分的標(biāo)記階段念链,當(dāng)對(duì)象引用改變時(shí),必須在下一次迭代中再次掃描這些對(duì)象积糯,如果在標(biāo)記階段不斷有引用關(guān)系的變化掂墓,可能導(dǎo)致標(biāo)記遍歷永遠(yuǎn)不能完成,這種情況下看成,垃圾收集會(huì)退回到進(jìn)行完整的非增量收集君编。
(2)另外,Incremental GC時(shí)川慌,引用發(fā)生改變啦粹,Unity就需要設(shè)置寫(xiě)屏障通知垃圾收集,會(huì)增加開(kāi)銷(xiāo)窘游,對(duì)代碼產(chǎn)生性能影響唠椭。
5、Native和Managed關(guān)系
如圖5-1忍饰,上半部分是Native Object創(chuàng)建過(guò)程贪嫂,下半部分是Managed Object創(chuàng)建過(guò)程。實(shí)際上不論我們是創(chuàng)建一個(gè)native object還是managed object都可能同時(shí)創(chuàng)建一個(gè)關(guān)聯(lián)的managed/native object艾蓝。
比如加載一個(gè)Texture會(huì)創(chuàng)建一個(gè)native object實(shí)例力崇,同時(shí)也會(huì)創(chuàng)建一個(gè)托管的Texture實(shí)例斗塘。
Texture _texture = Resources.Load<Texture>("Unity");
我們將_texture置為null,然后調(diào)用GC.Collect()亮靴;此時(shí)在Profiler中查看_texture實(shí)例并沒(méi)有被回收馍盟,而當(dāng)我們調(diào)用Resources.UnloadUnusedAssets()后_texture銷(xiāo)毀了。