GC相關(guān)

GC介紹

  1. GC(垃圾回收)是指不再被用到(廢棄、非激活狀態(tài))數(shù)據(jù)的內(nèi)存回收再次使用的過(guò)程(主要針對(duì)的是堆內(nèi)存的內(nèi)存管理)锨匆。

Unity內(nèi)存管理機(jī)制介紹

  1. Unity的內(nèi)存管理機(jī)制是采用自動(dòng)內(nèi)存管理的形式(減少開(kāi)發(fā)者對(duì)于內(nèi)存管理的關(guān)注,提高開(kāi)發(fā)效率)
  2. Unity的內(nèi)存管理區(qū)分:(1)堆棧內(nèi)存stack(用于存儲(chǔ)短時(shí)數(shù)據(jù)和較小數(shù)據(jù))(2)堆內(nèi)存heap(用于存儲(chǔ)長(zhǎng)時(shí)數(shù)據(jù)和較大數(shù)據(jù))
  3. 變量一旦被激活港华,其占用的內(nèi)存塊的狀態(tài)標(biāo)志會(huì)標(biāo)記為使用狀態(tài)脾歇,而變量不再被激活,其占用的內(nèi)存塊亦會(huì)被標(biāo)記為空閑狀態(tài)逗旁,則會(huì)被Unity的GC機(jī)制所檢測(cè)回收(堆棧上的回收是即時(shí)以及快速的夯秃,而堆內(nèi)存上并不是即時(shí)的,GC是一個(gè)定時(shí)的檢測(cè)回收機(jī)制痢艺,只有GC時(shí)才會(huì)回收堆內(nèi)存空間)仓洼。

堆棧上的內(nèi)存分配與回收機(jī)制介紹(值類(lèi)型變量)

類(lèi)似于stack(棧),創(chuàng)建即進(jìn)堤舒,無(wú)效則出色建,以一種順序且可控的形式進(jìn)行。(操作十分簡(jiǎn)潔舌缤,且快捷方便)

堆上的內(nèi)存分配與回收機(jī)制介紹(非值類(lèi)型變量)

相較于堆棧更復(fù)雜箕戳,且其回收順序是不可控的。

變量存儲(chǔ)在堆上的大致步驟:
  1. Unity會(huì)首先檢測(cè)堆內(nèi)存国撵,確定是否有足夠的閑置內(nèi)存單元來(lái)存儲(chǔ)變量陵吸,如果有,直接分配其對(duì)應(yīng)大小的內(nèi)存單元介牙。沒(méi)有則跳轉(zhuǎn)至第2步壮虫。
  2. 因?yàn)闆](méi)有足夠的存儲(chǔ)單元,此時(shí)Unity就會(huì)觸發(fā)GC(垃圾回收)機(jī)制來(lái)釋放那些被標(biāo)志為不再需要的堆內(nèi)存塊(該步驟十分緩慢)环础。如果此時(shí)垃圾回收后已經(jīng)有了足夠能存儲(chǔ)變量的內(nèi)存單元囚似,則直接進(jìn)行內(nèi)存分配,否則线得,跳轉(zhuǎn)至第3步饶唤。
  3. 因?yàn)槔厥蘸筮€是沒(méi)有足夠的存儲(chǔ)單元,此時(shí)Unity則會(huì)直接擴(kuò)展堆內(nèi)存的大小(該步驟極其緩慢)贯钩,然后再分配對(duì)應(yīng)大小的內(nèi)存單元給變量募狂。

GC的大致操作

  1. 遍歷檢測(cè)堆內(nèi)存上的變量。
  2. 檢測(cè)每個(gè)變量的引用的激活狀態(tài)角雷。
  3. 如果變量的引用不再處于激活態(tài)祸穷,則將其標(biāo)識(shí)為可回收狀態(tài)。
  4. 移除被標(biāo)識(shí)為可回收狀態(tài)的變量谓罗,將其所占有的內(nèi)存都回收到堆內(nèi)存上粱哼。

GC的觸發(fā)機(jī)制

  1. 在堆內(nèi)存上進(jìn)行內(nèi)存分配而內(nèi)存不夠的時(shí)候,觸發(fā)檩咱。
  2. 自動(dòng)觸發(fā)揭措。(不同平臺(tái)胯舷,運(yùn)行頻率不一樣)
  3. 強(qiáng)制觸發(fā)。(開(kāi)發(fā)者調(diào)用相應(yīng)函數(shù))

GC會(huì)帶來(lái)的問(wèn)題

  1. 降低游戲幀率绊含,使其運(yùn)行緩慢桑嘶。
    • 因?yàn)镚C操作可能會(huì)需要大量的時(shí)間來(lái)運(yùn)行,尤其是在其操作不僅進(jìn)行單純的內(nèi)存分配躬充,而且后續(xù)還進(jìn)行了內(nèi)存回收和內(nèi)存擴(kuò)充操作的時(shí)候逃顶。這種影響會(huì)在游戲運(yùn)行到關(guān)鍵時(shí)刻被放大,造成嚴(yán)重后果
    • 如果存在大量的變量和引用充甚,那么僅僅在第一步內(nèi)存檢測(cè)時(shí)以政,就會(huì)花費(fèi)大量的運(yùn)行時(shí)間。
  2. 堆內(nèi)存的碎片化伴找。
    • 內(nèi)存在分配時(shí)是會(huì)有不同的大小的盈蛮,當(dāng)它們被回收時(shí),其大小是不會(huì)改變的技矮,這就使得其在回收后依舊是其本身在分配后切割的大小抖誉,這就造成了一個(gè)結(jié)果——總的可使用的內(nèi)存單元較大,但單獨(dú)的內(nèi)存單元較小衰倦。內(nèi)存分配時(shí)袒炉,如果匹配不到合適大小的存儲(chǔ)單元,則依舊會(huì)觸發(fā)GC操作樊零,甚至是堆內(nèi)存擴(kuò)展操作我磁。
    • 碎片化造成的實(shí)際效果:
      (1). 游戲占用內(nèi)存越來(lái)越大。(經(jīng)過(guò)了堆擴(kuò)展)
      (2). GC會(huì)更加頻繁地被觸發(fā)淹接。

降低GC影響的方法

大致從三個(gè)方面入手:

  1. 減少GC的運(yùn)行次數(shù)
  2. 減少單次GC的運(yùn)行時(shí)間
  3. 控制GC的觸發(fā)時(shí)刻十性,避免關(guān)鍵時(shí)刻觸發(fā)

降低影響的策略:

  1. 重構(gòu)代碼:
    • 減少堆內(nèi)存分配(變量和引用的創(chuàng)建)叛溢,這樣就減少了GC操作中變量的檢測(cè)數(shù)量塑悼,從而提高GC運(yùn)行效率。
  2. 降低堆內(nèi)存分配和回收的頻率:
  3. 根據(jù)測(cè)試方案楷掉,調(diào)整GC的觸發(fā)時(shí)刻厢蒜,使其按照可預(yù)測(cè)的順序執(zhí)行。

實(shí)用方法:

  1. 緩存:對(duì)沒(méi)有改變卻被反復(fù)調(diào)用分配的變量進(jìn)行保存烹植,重復(fù)使用斑鸦。
  2. 不在頻繁調(diào)用的函數(shù)中進(jìn)行堆內(nèi)存分配。
  3. 容器類(lèi)草雕,不進(jìn)行多次創(chuàng)建巷屿,而是對(duì)單獨(dú)容器把持后,使用前進(jìn)行清理即可重復(fù)利用墩虹。
  4. 對(duì)象池:對(duì)需要頻繁的創(chuàng)建和銷(xiāo)毀的對(duì)象嘱巾,將其保存在固定容器中憨琳,反復(fù)回收利用,僅需要設(shè)置狀態(tài)即可旬昭。

造成不必要的堆內(nèi)存分配的因素

字符串:

C#中篙螟,字符串是引用類(lèi)型變量,而不是值類(lèi)型變量问拘,且其不可改變遍略,每次對(duì)其進(jìn)行變值操作,實(shí)質(zhì)都是會(huì)新建一個(gè)字符串來(lái)存儲(chǔ)骤坐,而舊的字符串會(huì)產(chǎn)生內(nèi)存垃圾绪杏,被廢棄回收。
因此可以采取一些方法來(lái)弱化字符串所帶來(lái)的影響:

  1. 減少不必要的字符串創(chuàng)建纽绍。(緩存多次利用的字符串)
  2. 減少不必要的字符串操作寞忿,可以分離不變值字符串,以及常變值字符串顶岸。
  3. 使用StringBuilder類(lèi)替代經(jīng)常變動(dòng)的字符串創(chuàng)建腔彰。
  4. 移除Debug.log()函數(shù),該函數(shù)即使輸出為空也會(huì)創(chuàng)建至少一個(gè)字符的字符串辖佣,如果游戲中大量調(diào)用霹抛,則會(huì)造成內(nèi)存垃圾的增加。

Unity函數(shù)調(diào)用:

在調(diào)用Unity中自帶的函數(shù)方法時(shí)卷谈,很容易在頻繁調(diào)用時(shí)杯拐,產(chǎn)生大量的內(nèi)存垃圾,需要根據(jù)實(shí)際來(lái)檢測(cè)使用世蔗。
例如:調(diào)用GameObject.name或者GameObject.tag時(shí)也會(huì)造成預(yù)想不到的堆內(nèi)存分配端逼,這兩個(gè)函數(shù)都會(huì)將結(jié)果存為新的字符串返回,這就造成了不必要的內(nèi)存垃圾污淋。
而為了避免這種情況顶滩,我們可以換用Unity中的GameObject.CompareTag函數(shù)來(lái)替代。

裝箱操作:

裝箱:當(dāng)值類(lèi)型作為引用類(lèi)型來(lái)使用時(shí)寸爆,觸發(fā)裝箱操作礁鲁,C#會(huì)將值類(lèi)型通過(guò)System.Object類(lèi)引用來(lái)封裝。
應(yīng)該盡量避免裝箱操作赁豆。

攜程:

調(diào)用StartCoroutine函數(shù)會(huì)產(chǎn)生少量的內(nèi)存垃圾仅醇,因?yàn)閁nity會(huì)生成實(shí)體來(lái)管理攜程,所以應(yīng)該在游戲的關(guān)鍵時(shí)刻嚴(yán)格限制攜程的調(diào)用魔种。
yield在攜程中不會(huì)產(chǎn)生堆內(nèi)存分配析二,但是如果它帶有參數(shù)返回,則會(huì)造成不必要的內(nèi)存垃圾节预。
yield return 0;
由于需要返回0叶摄,這里其實(shí)引發(fā)了裝箱操作漆改,所以產(chǎn)生了內(nèi)存垃圾,避免措施可以這樣做:
yeild return null;
另一種對(duì)攜程的錯(cuò)誤使用是准谚,在每次返回的時(shí)候都new同一個(gè)變量挫剑。

while(!isComplete)
{
    yield return new WaitForSeconds(1f);
}

這里可以采用緩存來(lái)避免內(nèi)存垃圾的產(chǎn)生:

WaitForSeconds delay = new WaitForSeconds(1f);
while(!isComplete)
{
    yield return delay;
}

foreach循環(huán)

在Unity5.5以前的版本,foreach的迭代中都會(huì)生成內(nèi)存垃圾柱衔,因?yàn)樵诘鷷r(shí)樊破,都會(huì)在堆內(nèi)存上生產(chǎn)一個(gè)System.Object用來(lái)實(shí)現(xiàn)迭代循環(huán)操作,在5.5版本后解決了這個(gè)版本唆铐。
所以在5.5版本前哲戚,可以采用for或者while循環(huán)來(lái)替代方案。

函數(shù)的引用

函數(shù)的引用艾岂,無(wú)論其指向的函數(shù)類(lèi)型顺少,都會(huì)在堆內(nèi)存上進(jìn)行內(nèi)存分配,所以最好減少函數(shù)的引用王浴。

LINQ和常量表達(dá)式

由于LINQ和常量表達(dá)式都是以裝箱的形式實(shí)現(xiàn)脆炎,所以使用時(shí)最好進(jìn)行性能測(cè)試。

重構(gòu)代碼來(lái)減小GC的影響

即使我們減小了代碼在堆內(nèi)存上的分配操作氓辣,代碼也會(huì)增加GC的工作量秒裕。最常見(jiàn)的增加GC工作量的方式是讓其檢查它不必檢查的對(duì)象。struct是值類(lèi)型的變量钞啸,但是如果struct中包含有引用類(lèi)型的變量几蜻,那么GC就必須檢測(cè)整個(gè)struct。如果這樣的操作很多体斩,那么GC的工作量就大大增加梭稚。在下面的例子中struct包含一個(gè)string,那么整個(gè)struct都必須在GC中被檢查:

public struct ItemData
{
    public string name;
    public int cost;
    public Vector3 position;
}
private ItemData[] itemData;

我們可以將該struct拆分為多個(gè)數(shù)組的形式絮吵,從而減小GC的工作量:

private string[] itemNames;
private int[] itemCosts;
private Vector3[] itemPositions;

簡(jiǎn)而言之弧烤,就是要減少GC檢測(cè)不必要變量的次數(shù)。

定時(shí)執(zhí)行GC操作源武。

在場(chǎng)景切換或是其他并不影響游戲性能的情況下扼褪,可以主動(dòng)進(jìn)行GC操作:

System.GC.Collec()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市粱栖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌脏毯,老刑警劉巖闹究,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異食店,居然都是意外死亡渣淤,警方通過(guò)查閱死者的電腦和手機(jī)赏寇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)价认,“玉大人嗅定,你說(shuō)我怎么就攤上這事∮貌龋” “怎么了渠退?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)脐彩。 經(jīng)常有香客問(wèn)我碎乃,道長(zhǎng),這世上最難降的妖魔是什么惠奸? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任梅誓,我火速辦了婚禮,結(jié)果婚禮上佛南,老公的妹妹穿的比我還像新娘梗掰。我一直安慰自己,他們只是感情好嗅回,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布愧怜。 她就那樣靜靜地躺著,像睡著了一般妈拌。 火紅的嫁衣襯著肌膚如雪拥坛。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天尘分,我揣著相機(jī)與錄音猜惋,去河邊找鬼。 笑死培愁,一個(gè)胖子當(dāng)著我的面吹牛著摔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播定续,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼谍咆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了私股?” 一聲冷哼從身側(cè)響起摹察,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎倡鲸,沒(méi)想到半個(gè)月后供嚎,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年克滴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逼争。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡劝赔,死狀恐怖誓焦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情着帽,我是刑警寧澤杂伟,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站启摄,受9級(jí)特大地震影響稿壁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜歉备,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一傅是、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蕾羊,春花似錦喧笔、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至利凑,卻和暖如春浆劲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背哀澈。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工牌借, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人割按。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓膨报,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親适荣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子现柠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354