Unity3D中的Mono內存管理

什麼是Mono內存

Unity引擎託管堆內存是由Mono分配和管理的。“託管”的本意是Mono可以自動地改變堆的大小來適應你所需要的內存,並且適時地調用垃圾回收(Garbage Collection)操作來釋放已經不需要的內存,從而降低開發人員在代碼內存管理方面的門檻。

Unity遊戲在運行時的內存佔用情況可以用下圖表示:
在這裏插入圖片描述
目前絕大部分Unity遊戲邏輯代碼所使用的語言爲C#,C#代碼所佔用的內存又稱爲mono內存,這是因爲Unity是通過mono來跨平臺解析並運行C#代碼的,在Android系統上,遊戲的lib目錄下存在的libmono.so文件,就是mono在Android系統上的實現。C#代碼通過mono解析執行,所需要的內存自然也是由mono來進行分配管理,下面就介紹一下mono的內存管理策略以及內存泄漏分析。

Mono內存管理策略

Mono通過垃圾回收機制(Garbage Collect,簡稱GC)對內存進行管理。

Mono內存分爲兩部分,已用內存(used)和堆內存(heap),已用內存指的是mono實際需要使用的內存,堆內存指的是mono向操作系統申請到的內存,兩者的差值就是mono的空閒內存。

當mono需要分配內存時,會先查看空閒內存是否足夠,如果足夠的話,直接在空閒內存中分配,否則mono會進行一次GC以釋放更多的空閒內存,如果GC之後仍然沒有足夠的空閒內存,則mono會向操作系統申請內存,並擴充堆內存,具體如下圖所示:
在這裏插入圖片描述
Mono中的GC主要有以下幾個步驟:
1.停止所有需要mono內存分配的線程。
2.遍歷所有已用內存,找到那些不再需要使用的內存,並進行標記。
3.釋放被標記的內存到空閒內存。
4.重新開始被停止的線程。

除了空閒內存不足時mono會自動調用GC外,也可以在代碼中調用GC.Collect()手動進行GC,但是,GC本身是比較耗時的操作,而且由於GC會暫停那些需要mono內存分配的線程(C#代碼創建的線程和主線程),因此無論是否在主線程中調用,GC都會導致遊戲一定程度的卡頓,需要謹慎處理。另外,GC釋放的內存只會留給mono使用,並不會交還給操作系統,因此mono堆內存是隻增不減的

Mono內存泄漏分析

Mono是如何判斷已用內存中哪些是不再需要使用的呢?是通過引用關係的方式來進行的。Mono會跟蹤每次內存分配的動作,並維護一個分配對象表,當GC的時候,以全局數據區和當前寄存器中的對象爲根節點,按照引用關係進行遍歷,對於遍歷到的每一個對象,將其標記爲活的(alive)。
在這裏插入圖片描述
如上圖所示,假設A是處於全局數據區的一個對象,那麼在GC的時候將作爲根節點進行遍歷,由於B、C、D對象都可以由A遍歷到,因此被標記爲活的,E、F對象則沒有被標記。注意,由於引用關係是單向的,A引用了B並不代表B也引用了A,所以遍歷也只能單向進行。

由於GC以全局數據區和當前寄存器中的對象爲根節點進行遍歷,所以對象的被標記意味着該對象可以通過全局對象或者當前上下文訪問到,而沒有被標記的對象則意味着該對象無法通過任何途徑訪問到,即該對象“失聯”了,GC最終會將所有“失聯”的對象內存進行回收,上圖中的E和F將會在GC過程中被回收。

既然mono已經有了完善的GC機制,那是否還會存在內存泄漏呢?答案是肯定的,只是此處的內存泄漏需要重新定義一下,我們把對象已經不再需要使用卻沒有被GC回收的情況稱爲mono內存泄漏。Mono內存泄漏會使空閒內存減少,GC頻繁,mono堆不斷擴充,最終導致遊戲內存佔用的升高。下圖就是一個mono內存泄漏的例子:
在這裏插入圖片描述

Mono內存泄漏解決辦法

對於mono內存泄漏,一般只能通過猜測+不斷修改代碼測試的方法來修復問題,效率很低,下面結合具體的代碼看一個內存泄漏的例子。

public class A
{
	int[] a;
	public A()
	{
		a=new int[1000];
	}
}

static A objA;
void OnGUI(){
	if(GUI.Button(new Rect(10,50,160,120),"測試")){
		objA=new A();}

上面代碼我們定義類A,並在A的構造函數中申請了一塊int[1000]大小的內存。通過點擊按鈕,會在內存中申請int[100]內存,並由靜態對象objA引用。

需要注意的是,由於objA是靜態對象,是GC的根節點,GC遍歷是以它爲其中的一個根節點,來判定其它對象是否沒有被引用。因此GC無法直接回收objA,也就導致int[1000]無法被回收,從而導致Mono內存泄漏

若想要被回收,可以設置objA.a=null。這樣在GC遍歷objA的時候是可以發現int[1000]屬於未被引用的內存,從而被回收。(注意設置爲null,並不能立即回收內存,只是斷了引用關係,下一次GC的時候纔會把這塊沒有引用的內存給回收)

Cube工具

騰訊Wetest平臺的Cube工具提供了mono內存快照對比的功能,幷包括對象分配堆棧,對象引用關係等詳細信息,是定位mono內存泄漏問題的一大利器。

補充

據說Unity上Mono內存最好控制在30MB內(未看到具體文檔說明,待補充)

參考文檔

onevcat:Unity3D中的內存管理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章