資源描述:
《unity游戲mono內(nèi)存管理及泄漏》由會員上傳分享,免費(fèi)在線閱讀,更多相關(guān)內(nèi)容在行業(yè)資料-天天文庫。
1、內(nèi)存是手游的硬傷——Unity游戲Mono內(nèi)存管理及泄漏WeTest導(dǎo)讀內(nèi)存是游戲的硬傷,如果沒有做好內(nèi)存的管理問題,游戲極有可能會出現(xiàn)卡頓,閃退等影響用戶體驗(yàn)的現(xiàn)象。本文介紹了在騰訊游戲在Unity游戲開發(fā)過程中常見的Mono內(nèi)存管理問題,并介紹了一系列解決的策略和方法,無論是游戲還是VR應(yīng)用,內(nèi)存管理都是其研發(fā)階段的重中之重。什么是Mono內(nèi)存對于目前絕大多數(shù)基于Unity引擎開發(fā)的項(xiàng)目而言,其托管堆內(nèi)存是由Mono分配和管理的?!巴泄堋钡谋疽馐荕ono可以自動地改變堆的大小來適應(yīng)你所需要的內(nèi)存,并且適時(shí)地調(diào)
2、用垃圾回收(GarbageCollection)操作來釋放已經(jīng)不需要的內(nèi)存,從而降低開發(fā)人員在代碼內(nèi)存管理方面的門檻。Unity游戲在運(yùn)行時(shí)的內(nèi)存占用情況可以用下圖表示:目前絕大部分Unity游戲邏輯代碼所使用的語言為C#,C#代碼所占用的內(nèi)存又稱為mono內(nèi)存,這是因?yàn)閁nity是通過mono來跨平臺解析并運(yùn)行C#代碼的,在Android系統(tǒng)上,游戲的lib目錄下存在的libmono.so文件,就是mono在Android系統(tǒng)上的實(shí)現(xiàn)。C#代碼通過mono解析執(zhí)行,所需要的內(nèi)存自然也是由mono來進(jìn)行分配管理,
3、下面就介紹一下mono的內(nèi)存管理策略以及內(nèi)存泄漏分析。Mono內(nèi)存管理策略Mono通過垃圾回收機(jī)制(GarbageCollect,簡稱GC)對內(nèi)存進(jìn)行管理。Mono內(nèi)存分為兩部分,已用內(nèi)存(used)和堆內(nèi)存(heap),已用內(nèi)存指的是mono實(shí)際需要使用的內(nèi)存,堆內(nèi)存指的是mono向操作系統(tǒng)申請的內(nèi)存,兩者的差值就是mono的空閑內(nèi)存。當(dāng)mono需要分配內(nèi)存時(shí),會先查看空閑內(nèi)存是否足夠,如果足夠的話,直接在空閑內(nèi)存中分配,否則mono會進(jìn)行一次GC以釋放更多的空閑內(nèi)存,如果GC之后仍然沒有足夠的空閑內(nèi)存,則mo
4、no會向操作系統(tǒng)申請內(nèi)存,并擴(kuò)充堆內(nèi)存,具體如下圖所示。通過上文可知,GC的主要作用在于從已用內(nèi)存中找出那些不再需要使用的內(nèi)存,并進(jìn)行釋放。Mono中的GC主要有以下幾個(gè)步驟:1.停止所有需要mono內(nèi)存分配的線程。2.遍歷所有已用內(nèi)存,找到那些不再需要使用的內(nèi)存,并進(jìn)行標(biāo)記。3.釋放被標(biāo)記的內(nèi)存到空閑內(nèi)存。4.重新開始被停止的線程。除了空閑內(nèi)存不足時(shí)mono會自動調(diào)用GC外,也可以在代碼中調(diào)用GC.Collect()手動進(jìn)行GC,但是,GC本身是比較耗時(shí)的操作,而且由于GC會暫停那些需要mono內(nèi)存分配的線程(
5、C#代碼創(chuàng)建的線程和主線程),因此無論是否在主線程中調(diào)用,GC都會導(dǎo)致游戲一定程度的卡頓,需要謹(jǐn)慎處理。另外,GC釋放的內(nèi)存只會留給mono使用,并不會交還給操作系統(tǒng),因此mono堆內(nèi)存是只增不減的。Mono內(nèi)存泄漏分析Mono是如何判斷已用內(nèi)存中哪些是不再需要使用的呢?是通過引用關(guān)系的方式來進(jìn)行的。Mono會跟蹤每次內(nèi)存分配的動作,并維護(hù)一個(gè)分配對象表,當(dāng)GC的時(shí)候,以全局?jǐn)?shù)據(jù)區(qū)和當(dāng)前寄存器中的對象為根節(jié)點(diǎn),按照引用關(guān)系進(jìn)行遍歷,對于遍歷到的每一個(gè)對象,將其標(biāo)記為活的(alive)。如上圖所示,假設(shè)A是處于全局
6、數(shù)據(jù)區(qū)的一個(gè)對象,那么在GC的時(shí)候?qū)⒆鳛楦?jié)點(diǎn)進(jìn)行遍歷,由于B、C、D對象都可以由A遍歷到,因此被標(biāo)記為活的,E、F對象則沒有被標(biāo)記。注意,由于引用關(guān)系是單向的,A引用了B并不代表B也引用了A,所以遍歷也只能單向進(jìn)行。由于GC以全局?jǐn)?shù)據(jù)區(qū)和當(dāng)前寄存器中的對象為根節(jié)點(diǎn)進(jìn)行遍歷,所以對象的被標(biāo)記意味著該對象可以通過全局對象或者當(dāng)前上下文訪問到,而沒有被標(biāo)記的對象則意味著該對象無法通過任何途徑訪問到,即該對象“失聯(lián)”了,GC最終會將所有“失聯(lián)”的對象內(nèi)存進(jìn)行回收,上圖中的E和F將會在GC過程中被回收。既然mono已經(jīng)有
7、了完善的GC機(jī)制,那是否還會存在內(nèi)存泄漏呢?答案是肯定的,只是此處的內(nèi)存泄漏需要重新定義一下,我們把對象已經(jīng)不再需要使用卻沒有被GC回收的情況稱為mono內(nèi)存泄漏。Mono內(nèi)存泄漏會使空閑內(nèi)存減少,GC頻繁,mono堆不斷擴(kuò)充,最終導(dǎo)致游戲內(nèi)存占用的升高。下圖就是一個(gè)mono內(nèi)存泄漏的例子。解決辦法對于mono內(nèi)存泄漏,一般只能通過猜測+不斷修改代碼測試的方法來修復(fù)問題,效率很低,騰訊Wetest平臺的Cube工具提供了mono內(nèi)存快照對比的功能,并包括對象分配堆棧,對象引用關(guān)系等詳細(xì)信息,是定位mono內(nèi)存泄漏
8、問題的一大利器。下面結(jié)合具體的代碼嘗試使用Cube定位mono內(nèi)存泄漏問題。首先我們定義類A,并在A的構(gòu)造函數(shù)中申請了一塊int[1000]大小的內(nèi)存。接著我們定義A類型的靜態(tài)變量objectA,在游戲界面上繪制一個(gè)按鈕,并在按鈕點(diǎn)擊事件中給objectA賦值,此時(shí)新生成了newint[1000]對象,并由objectA引用。使用Cube的mono內(nèi)存檢測功能,并在按鈕按