要出現這種內存泄露,必須先準備壹塊任意的內存塊:(無任何外部引用,理論上應該會在用完後被GC,但在該BUG下會錯誤的泄露,不被GC掉)
NOTE:大小任意,越大越容易被泄露。
壹塊struct結構的數組:(struct內必須有壹個類似指針的值類型, 如int,和另壹個引用類型,如string)
NOTE:大小任意,數組內的元素越多越容易觸發泄露。例如 HashSet<String> 內部使用了該數據格式。
通過在GC中打點,和使用GDB調用GC過程,以便觀察所有對象的分配和GC的過程發現:buffer對象錯誤的被slots對象引用,導致buffer對象無法被正常GC,造成內存泄露。
首先對於mono/il2cpp的Boehm GC庫而言, mono/il2cpp的對象在分配內存的時候,會有幾種類型:
而在本例中:slots的分配是用NORMAL類型,buffer對象的分配是用PTRFREE類型。
因此在做GC的時候,對於slots對象,GC會掃描該對象的內存區間,查找其內部的指針地址,即從0xde45f000到0xde468c50地址按照指針對齊的方式查找指針地址:
例如:0xde45f000 0xde464d44 0xde464f40 0xde46513c ....
其中出現了 0xde464f40 這個地址的值剛好為:0xbe82f000(即Slot結構體內hashCode的值),而GC會錯誤的將該int型數值當做指針,而該指針剛好又指向了壹塊GC托管的內存塊,即buffer對象,因此GC認為該buffer對象被slots對象內部引用了,buffer對象也被GC標記,不會被釋放。
該問題的關鍵在於,GC將slot結構體內的hashcode這個int值錯誤的當做的指針,而該int值剛好又指向了另壹個托管的對象,因此GC錯認為了兩個對象存在引用關系,而造成內存泄露。
最小化Demo:
將struct Slot修改為class Slot,則可修復內存泄露問題,因為class對象的內存分配時TYPED類型。
因為Mono的GC的設計問題,Unity遊戲中幾乎不可避免的都會隨著時間出現內存泄露問題,因為例如HashSet這種數據結構內部都會出現該問題。但我們可以做的事情,依然是內存使用的兩大真理(特別是虛擬機類型的語言):
這樣做,不能完全避免Mono的底層GC問題,但是它可以讓這種內存泄露的變得更加平緩。