項目一運行,占用的內存越來越多,不會釋放,導致GC越來越頻繁,越來越慢,這些都是為什么呢,今天從UI方面談起。
首先讓我們來聊聊什么是內存泄漏呢?
一般來講內存泄漏就是指我們的應用向內存申請了一塊地址,然后這塊地址的相關引用全部丟失了,這塊內存無法再被分配,在計算機眼里,那就是丟了,找不回來了,除非重啟。。。
不過,這里如果我們要去理解Unity中的內存泄漏,那我們首先要了解一下Unity的內存分配機制和GC機制,哇,不過說真的,要真是細說這兩點,那真是幾天都講不完呀,還是算了,哈哈,這里大概聊一下,
程序在運行的時候,會先從計算機中申請一塊內存,這時候如果我們需要去申請一塊地址的時候,Unity會先去從堆內存中找合適大小的地址塊給我們,但是這時候,如果堆內存用完了,這時候GC就出馬了,會先清理一遍當前內存中無用的數據然后給我們分配所需要的內存塊,那這個時候如果GC之后還是沒有找到足夠大小的內存給我們用怎么辦呢,Unity只能去在申請一塊之前內存2被大小的內存了。
這時候來想想,如果在我們的項目中這如果不斷重復上述步驟,那么這時候是不是就意味著內存泄漏了呢。。。? 現在就讓我們開始從實際情況來一探究竟吧!!!
一開始我們通過Unity的Profiler工具只能看到在我們的UI已經關閉銷毀了可是UI里面用到的圖集還在內存里面存在,不應該呀,如果圖集不釋放,那豈不是意味著我們如果打開很多UI的時候,這些圖集資源就要占到很多內存,如何查看當前內存中圖集情況,可以參考下圖,先選中Memory模塊,然后選擇Detailed,點擊Take Sample Playmode,這時候內存中的圖集就出現在下面了,參考5的位置,這里說明一下位置4這個選項,如果不勾選,進行內存采樣速度會快很多,勾選了會慢很多,但是會同時采樣出對應資源當前的引用情況。
這時候我們通過對游戲中不同節點進行內存采樣,便能分析出我們哪些圖集沒有隨著預設的銷毀而銷毀。
問題已經找到了,那么如何解決呢,如何下手呢,這時候又不知道怎么辦了,害!!!
?但是生活還要繼續,問題還得解決呀,那么接下來就開始了問題分析,無數次Demo測試,從AB包加載卸載,到Unity內存分配管理,從GC的工作方式,到GC的底層實現原理,終于發現了這幾個問題。
首先,如果我們的項目是通過AssetBundle方式加載的,那么在我們切場景或者進行階段變化的時候我們需要處理一下無用資源的釋放,調用一下下面的接口。
Resources.UnloadUnusedAssets();
?卸載未使用的資源
?這時候我們在進行內存對比分析的時候會發現會有一些內存被釋放,可是圖集不銷毀的問題還在,害,還以為挺簡單的,目前看來問題更復雜了。。。
這時候用上了另一個工具Memory Profiler,這個工具是在Unity2020之后的版本推出的功能,對當前內存進行快照,可視化的形式顯示當前內存分配的大小,列出了每個托管對象的類型,值,占用大小,地址,被引用鏈等等信息,還可以進行快照對比,分析兩次內存快照新增、刪除和保持不變的內存對象,從而更方便快捷的定位項目內存的使用情況。
通過對內存進行快照,分析圖集的引用鏈,屏蔽代碼,重新快照測試,一次次的測試,慢慢縮小代碼范圍,定位圖集不銷毀的原因,最終發現原來是我們的UI使用了static實例來實現單例效果,在其他地方調用,但是在我們UI不需要的時候并沒有將這個靜態單例設置為null,導致整個UI資源的相關引用一直存在,無法釋放,還有就是我們在對按鈕進行事件注冊的時候,使用了項目封裝的接口,而項目封裝的接口在拿到委托事件對象后,并沒有在移除事件的時候去清除委托事件對象,導致引用一直存在,相關的資源也就無法釋放。
相信經過上述步驟之后我們的圖集不銷毀問題已經解決了大部分了,具體還有哪些,后面有需要我們再補充,哈哈。
這里再說一個圖片不銷毀問題,在項目中我們經常會去動態替換某些圖片來實現我們的功能,這時候有一個統一接口就很方便了,可是圖片不銷毀問題也正好跟這個動態替換接口有關,由于我們的統一接口會保存一份加載的圖片的引用,在對應預設銷毀的時候,由于圖片引用一直存在,所以圖片就無法被GC處理掉,這時候我們可以考慮對我們動態加載的圖片進行場景管理,在合適的時候清空一次引用列表,還有由于我們動態圖片加載是自己管理加載資源,所以我們在清空列表的時候要調用一次對應接口的卸載資源接口,否則,資源還是無法從內存中釋放。
目前為止,圖集圖片不銷毀問題已經解決了大部分,至于項目中具體還有沒有其他問題導致,有待后續研究,,,總結一下:
- 使用了static靜態類方式來實現單例的UI,在使用完之后一定記得將對應單例設置為null,讓GC可以去釋放對應的內存。
- 在使用委托或者其他時候,拿到類對象的引用在使用完之后一定要記得釋放引用。
- 加載的資源在不適用的時候記得卸載掉,比如AssetBundle.Load()和AssetBundle.Unload()
- 在適當的時機調用Resource.UnloadUnusedAssets()接口釋放無用的資源
簡而言之,言而簡之,內存優化一直是項目開發中的重頭戲,任重而道遠呀。。。
?心懷夢想? ? 奔向遠方?