https://blog.csdn.net/jinzhuojun/article/details/9292467
Mono在2.10版本之前集成BOEHM內存管理崖媚,2.11及更高版本使用SGen內存管理
Unity中Mono版本一直停留在2.10前栈幸。
1淳附、
1.1? ? ? BOEHM GC使用的是Mark-Sweep算法。
BOEHM GC基本步驟:
(1)標記階段:從根節(jié)點遍歷所有被引用的對象并標記蒙保;
(2)清除階段:將所有未標記對象內存釋放辕棚。
BOEHM GC是一種保守式的GC算法,只需要截斷原來的malloc()和free()接口替換為自己的GC_malloc()接口。
1.2? ? ?(以下所有計算均按64位操作系統(tǒng)處理)BOEHM內存分配根據內存大小分為小對象分配邏輯和大對象分配邏輯逝嚎,如果申請小于等于2048(2^12 / 2)字節(jié)則執(zhí)行小對象分配邏輯扁瓢,否則執(zhí)行大對象分配。小對象分配會包含三種內存分配:普通內存懈糯、指針內存涤妒、非回收內存。GC_obj_kinds數組用來區(qū)分三種不同類型的內存赚哗,所以該數組長度為3她紫。每一種類型都維護一個空閑內存鏈表數組ok_freelist,ok_freelist是一個長度為MAXOBJGRANULES(MAXOBJGRANULES為128)+1的數組屿储,每個數組元素指向一個空閑鏈表贿讹,該鏈表每一個元素指向一個大小為index*16的空閑內存塊。其中index為該鏈表所在數組的索引够掠,16為最小內存分配粒度民褂,128*16=2048即為小對象最大分配上限。
小對象內存分配疯潭,首先會查找GC_size_map表找到內存大小對應的內存分配索引赊堪。其中GC_size_map是一個長度為MAXOBJBYTES(MAXOBJBYTES為2048即最大分配內存上限)+1的數組,該數組的key代表申請內存大小竖哩,value即為內存分配索引哭廉。然后根據索引查找對應類型的GC_objfreelist,如果存在空閑內存塊則直接返回對應指針并將該內存塊從空閑鏈表移除相叁。否則執(zhí)行一次GC遵绰,如果GC后還沒有空閑列表則需要分配新的空閑內存塊,分配內存時增淹,首先根據申請內存大小通過公式(bytes+PAGE_SIZE-1)/PAGE_SIZE得到一個index椿访,從index開始遍歷GC_hblkfreelist數組,直到找到一個空閑塊虑润。(其中GC_hblkfreelist是一個類似于ok_freelist的數組成玫,長度為N_HBLK_FLS(N_HBLK_FLS為28)+1,每個元素也存儲一個空閑內存鏈表拳喻,每個鏈表元素指向一個PAGE_SIZE*index大小的內存塊梁剔,其中PAGE_SIZE為4096,index即為所在數組索引舞蔽。)然后將該空閑塊拆成一個PAGE塊和另一個塊,另一個塊放回GC_hblkfreelist中码撰,PAGE_SIZE塊根據申請大小拆成空閑塊列表返回給ok_freelist渗柿。
如果是大對象內存分配,直接查找GC_hblkfreelist分配一個內存塊。
每個PAGE塊分配時會生成一個HBLKHDR結構朵栖,該結構會記錄hb_sz颊亮、 hb_marks、descr等信息陨溅,其中h b_sz為拆分成ok_freelist塊的大小终惑,hb_marks標記拆分塊使用狀態(tài)。HBLKHDR結構用一個二維數組記錄门扇,每個HBLKHDR的存儲位置為[p>>22][p>>12 & 1024]雹有,其中P代表PAGE塊起始地址。HBLKHDR由非回收內存分配臼寄。
2霸奕、SGen(Simple Generational GC)是一種分代式垃圾回收器
SGen特點:
(1)分兩代:the nursery generation、the old generation吉拳;
(2)Stopiing the world:垃圾收集時质帅,所有運行線程需要停止;
(3)兩種主要收集方式:
minor collection:針對nursery generation留攒,采用copying collection煤惩;
major collection:針對the old generation,使用mark-sweep方式炼邀。
(4)Write barriers?
為了減少GC開銷魄揉,有時可能只需要進行nursery collection而不是完整的回收,但可能存在old generation中對象指向nursery中對象的情況:
這種情況就需要write barriers解決汤善,Mono中通過一個cardtables實現write barriers什猖。Cardtable會把major heap分為固定大小的塊(SGen中為512bytes)稱為card,每個card中有一個字節(jié)用來標記該card是否有對nursery中對象的引用红淡,如果有的話不狮,就會掃描該card中所有塊,找到引用的位于nursery中的對象進行標記Copy在旱。
(5)多線程
標記階段可以多個線程并行標記摇零,每一個線程包含一部分根對象,每一個線程有自己的gray stack,當多個線程同時遍歷到同一個引用對象時桶蝎,只有第一個找到該引用對象的線程才會將該對象壓入自己的gray stack進行后續(xù)處理驻仅。
2.1? ?minor collection和major collection:
(1)minor collection 主要針對nursery generation。步驟如下:
a登渣、識別并且標記 Pinned Objects
Pinned Object分為兩種:
第一種是使用fixed修飾符指定或者通過P/Invoke傳遞給非托管代碼使用的對象引用噪服,這種對象創(chuàng)建時就會標記為PINNED;
第二種就是通過保守式掃描棧和線程寄存器,找到可能引用的對象也標記為PINNED胜茧。
每個Mono Object創(chuàng)建時都會創(chuàng)建一個vtable指針粘优,vtable的低三位標記對象的狀態(tài)仇味,如圖2-1所示:
b、遍歷掃描所有roots找到所有引用雹顺,所有可達對象會被拷貝到the old generation并被標記為FORWARDED丹墨。整個過程使用一個gray stack實現,不斷將可達對象壓入棧中嬉愧,然后彈出棧中對象遍歷其引用對象再壓入棧贩挣,直到棧為空,所有引用對象處理完畢没酣。
c王财、清除未標記和非pinnded Object,如果是小對象會將空閑空間放到一個freelist供下次分配。
(2)major collection 主要針對the old generation四康,使用mark-sweep方式實現搪搏,標記階段和minor collection一樣,也是從root遍歷查找標記所有引用對象闪金,sweep階段會將未標記對象釋放疯溺,然后統(tǒng)計每個block中slot的占用率,如果指定slot大小的占用率低于設置的閾值時哎垦,會將該指定大小的slot順序拷貝到一個新分配的blocks中囱嫩,并將原slot置空等待下次回收并且重新構建free lists。
另外Mono對major collection的sweep階段進行了優(yōu)化即concurrent sweep漏设,sweep階段做的工作就是遍歷blocks墨闲,重置標記位,置空垃圾對象占用的slot以及重新構建free lists郑口,這些操作都不會影響minor collection鸳碧,所以minor collection可以和concurrent sweep并行進行。
2.2? ?內存分配
(1)小對象內存分配
Sgen初始化默認會分配一個4MB的連續(xù)空間作為nursery generation空間犬性,當小于等于8KB的對象申請空間時瞻离,會在the nursery generation中查找可用空間,如果已滿不足分配則會觸發(fā)minor collection乒裆。如果在多線程環(huán)境下套利,每個線程會分配一小塊nursery,Mono把它稱為TLABs(thread local allocation buffers)鹤耍,目前TLABs固定大小為4KB肉迫。
the old generation 會分配多個固定大小為16KB的Block,每個Block分割成等大小的slot稿黄,所有的slot記錄在一個free lists中便于分配喊衫。
(2)大于8KB的對象,Sgen會視為大對象杆怕,直接從操作系統(tǒng)申請內存空間族购,使用完后直接歸還給操作系統(tǒng)鼻听。