337p人体粉嫩胞高清图片,97人妻精品一区二区三区在线 ,日本少妇自慰免费完整版,99精品国产福久久久久久,久久精品国产亚洲av热一区,国产aaaaaa一级毛片,国产99久久九九精品无码,久久精品国产亚洲AV成人公司
網易首頁 > 網易號 > 正文 申請入駐

Unity IL2CPP的GC原理

0
分享至


【USparkle專欄】如果你深懷絕技,愛“搞點研究”,樂于分享也博采眾長,我們期待你的加入,讓智慧的火花碰撞交織,讓知識的傳遞生生不息!

這是侑虎科技第1924篇文章,感謝作者Jamin發供稿。歡迎轉發分享,未經作者授權請勿轉載。如果您有任何獨到的見解或者發現也歡迎聯系我們,一起探討。(QQ群:793972859)

作者主頁:

https://www.zhihu.com/people/liang-zhi-ming-70

背景:前段時間在項目內做了關于Mono內存(堆內存)的優化。從結果上將Mono內存從220MB降低到130MB,優化過程中喚起了部分關于GC的消失的回憶,雖然實際的優化工作中也許并用不到,但是更明確底層實現機制總歸是一件迭代自我的過程,在這里就來回顧一下。

一、什么是垃圾回收 - GC(Garbage Collector)

在游戲運行的時候,數據主要存儲在內存中,當游戲的數據在不需要的時候,存儲當前數據的內存就可以被回收以再次使用。內存垃圾是指當前廢棄數據所占用的內存,垃圾回收(GC)是指將廢棄的內存重新回收再次使用的過程。

1. 什么時候觸發垃圾回收

有三個操作會觸發垃圾回收:

  • 在堆內存上進行內存分配操作而內存不夠的時候都會觸發垃圾回收來利用閑置的內存。

  • GC會自動觸發,不同平臺運行頻率不一樣。

  • GC被代碼強制執行

2. GC操作帶來的問題

直白點就兩個問題:一個是Stop-the-world導致的“卡”;一個是內存碎片導致的“堆內存太大”。

  • GC操作會需要大量的時間來運行,如果堆內存上有大量的變量或者引用需要檢查,則檢查的操作會十分緩慢,這就會使得游戲運行緩慢。

  • GC可能會在關鍵時候運行,例如在CPU處于游戲的性能運行關鍵時刻,此時任何一個額外的操作都可能會帶來極大的影響,使得游戲幀率下降。

  • 另外一個GC帶來的問題是堆內存的碎片。當一個內存單元從堆內存上分配出來,其大小取決于其存儲的變量的大小。當該內存被回收到堆內存上的時候,有可能使得堆內存被分割成碎片化的單元。也就是說堆內存總體可以使用的內存單元較大,但是單獨的內存單元較小,在下次內存分配的時候不能找到合適大小的存儲單元,這也會觸發GC操作或者堆內存擴展操作。

  • 堆內存碎片會造成兩個結果:一個是游戲占用的內存會越來越大;一個是GC會更加頻繁地被觸發。

特別是在堆內存上進行內存分配時內存單元不足夠的時候,GC會被頻繁觸發,這就意味著頻繁在堆內存上進行內存分配和回收會觸發頻繁的GC操作。

二、Unity托管堆

在講具體的Unity GC機制之前再回顧一下Unity托管堆。

1. 托管堆的工作原理及其擴展原因

“托管堆”是由項目腳本運行時(Mono或IL2CPP)的內存管理器自動管理的一段內存。必須在托管堆上分配托管代碼中創建的所有對象。


Unity官方文檔圖

在上圖中,白框表示分配給托管堆的內存量,而其中的彩色框表示存儲在托管堆的內存空間中的數據值。當需要更多值時,將從托管堆中分配更多空間。

GC定期運行將掃描堆上的所有對象,將任何不再引用的對象標記為刪除。然后會刪除未引用的對象,從而釋放內存。

至關重要的是,Unity的垃圾收集是非分代的,也是非壓縮的。“非分代”意味著GC在執行每遍收集時必須掃描整個堆,因此隨著堆的擴展,其性能會下降。“非壓縮”意味著不會為內存中的對象重新分配內存地址來消除對象之間的間隙。


內存空隙

上圖為內存碎片化示例。釋放對象時,將釋放其內存。但是,釋放的空間不會整合成為整個“可用內存”池的一部分。位于釋放的對象兩側的對象可能仍在使用中。因此,釋放的空間成為其他內存段之間的“間隙”(該間隙由上圖中的紅色圓圈指示)。因此,新釋放的空間僅可用于存儲與釋放相同大小或更小的對象的數據。

這導致了內存碎片化這個核心問題:雖然堆中的可用空間總量可能很大,但是可能其中的部分或全部的可分配空間對象之間存在小的“間隙”。這種情況下,即使可用空間總量高于要分配的空間量,托管堆可能也找不到足夠大的連續內存塊來滿足該分配需求。


如果分配了大型對象又沒有足夠的連續空間提供使用則:

  • 運行垃圾回收器,嘗試釋放空間來滿足分配請求。

  • 如果在GC運行后,仍然沒有足夠的連續空間來滿足請求的內存量,則必須擴展堆。堆的具體擴展量視平臺而定。

2. Unity托管堆的問題

  • Unity在擴展托管堆后不會經常釋放分配給托管堆的內存頁,防止再次發生大量分配時需要重新擴展堆

  • 在大多數平臺上,Unity最終會將托管堆的空置部分使用的頁面釋放回操作系統。發生此行為的間隔時間是不確定的,不要指望靠這種方法釋放內存。

頻繁分配臨時數據給托管堆,這種情況通常對項目的性能極為不利

如果每幀分配1KB的臨時內存,并且以60幀的速率運行,那么它必須每秒分配60KB的臨時內存。在一分鐘內,這會在內存中增加3.6MB的垃圾。對內存不足的設備而言每分鐘3.6MB的垃圾也無法接受。

三、Unity的GC機制 -- Boehm GC

以前看過Unity使用的GC方案但最近才驚覺現在使用的Unity都是IL2CPP的版本了,所謂的Mono GC本來就已經不存在了。于是來看下現在的IL2CPP的GC機制: Boehm GC(貝姆垃圾收集器)。

1. IL2CPP - Boehm GC

貝姆垃圾收集器是計算機應用在C/C++語言上的一個保守的垃圾回收器(Garbage Collector),可應用于許多經由C\C++開發的程序中。

摘錄一段定義:

Boehm-Demers-Weiser garbage collector,適用于其它執行環境的各類編程語言,包括了GNU版Java編譯器執行環境,以及Mono的Microsoft .NET移植平臺。同時支援許多的作業平臺,如各種Unix操作系統,微軟的操作系統(Microsoft Windows),以及麥金塔上的操作系統(Mac OS X),還有更進一步的功能,例如:漸進式收集(Incremental Collection),平行收集(Parallel Collection)以及終結語意的變化(Variety Offinalizersemantics)。

在Unity中我們可以看到關于Boehm GC的算法部分:


BoehmGC.cpp內部調用的就是這個第三方庫,他是Stop-the-world類型的垃圾收集器,這表明了在執行垃圾回收的時候,將會停止正在運行的程序,而停止時間的只有在完成工作后才會恢復,所以這就導致了GC引起的程序卡頓峰值,很顯然這對游戲的平滑體驗造成了較大的負面影響。

通常,解決這個問題的常規方案是盡可能地“減少”運行時垃圾回收(后續用GC代替),亦或者將GC放在不那么操作敏感的場景中,比如回城、死亡后等。但完全避免運行時垃圾回收在大部分時間是不現實的。

接下來我們來看看Boehm GC的背后機制。

2. Boehm GC算法思路

Boehm GC是一種Mark-Sweep(標記-清掃)算法,大致思路包含了四個階段:

  • 準備階段:每個托管堆內存對象在創建出來的時候會有一個關聯的標記位,來表示當前對象是否被引用,默認為0。

  • 標記階段:從根內存節點(靜態變量;棧;寄存器)出發,遍歷掃描托管堆的內存節點,將被引用的內存節點標記為1。

  • 清掃階段:遍歷所有節點,將沒有被標記的節點的內存數據清空,并且基于一定條件釋放。

  • 結束階段:觸發注冊過的回調邏輯。

3. 漸進式GC

使用漸進式GC允許把GC工作分成多個片,因此為了不讓GC工作長時間的“阻塞”主線程,將其拆分成了多個更短的中斷。需要明確的是這并不會使GC總體上變得更快,但是卻可以將工作負載分配到多幀來平緩單次GC峰值帶來的卡頓影響。

注: Unity在高版本已經默認是漸進式GC了,大概是Unity 19.1a10版本。

[Unity 活動]-淺談Unity內存管理_嗶哩嗶哩_bilibili


https://www.bilibili.com/video/BV1aJ411t7N6/?vd_source=60173b91c5d0a0bed2ae426307dcc6b5

4. GC中的內存分配

Boehm GC的使用方法非常簡單,只需要將malloc替換為GC_malloc即可,在此之后便無需關心free的問題。

void * GC_malloc(size_t lb)
{
return GC_malloc_kind(lb, NORMAL);
}


void * GC_malloc_kind(size_t lb, int k)
{
return GC_malloc_kind_global(lb, k);
}

在整個內存分配鏈的最底部,Boehm GC通過平臺相關接口來向操作系統申請內存。為了提高申請的效率,每次批量申請4KB的倍數大小。

分配器的核心是一個分級的結構,Boehm GC把每次申請根據內存大小歸類成小內存對象和大內存對象。

  • 小內存對象:不超過PageSize/2,小于2048字節的對象。

  • 大內存對象:大于PageSize/2的對象。

對于大內存對象,向上取整到4KB的倍數大小,以整數的內存塊形式給出。而小內存對象則會先申請一個內存塊出來,而后在這塊內存上進一步細分為Small Objects,形成free-list。

下面會分別說下大內存對象和小內存對象,參考網上的資料整理,確實有點點干,但是配圖我重新做了一下,大概可以輔助消化。

四、IL2CPP - Boehm GC:小內存分配

1. 粒度對齊

實現思路是,提出粒度(GRANULES)的概念,即一個GRANULE的大小是16字節。實際分配內存的時候按照GRANULE為基本單位來分配。分配過程中,按照原始需要的大小,計算并映射得到實際需要分配的GRANULE個數,代碼如下:

//lb是原始的分配大小,lg是GRANULE(1~128)。
size_t lg = GC_size_map[lb];

例如需要18字節的內存,則lg=2,即實際分配2個GRANULE(32字節),如果需要1字節的內存,則lg=1,即實際分配1個GRANULE(16字節)。

GC_size_map是一個“GRANULE索引映射表”,用來維護原始分配的內存大小和內存索引之間的關系。最多可以返回128個GRANULE,所以小內存的大小上限是128*16=2048。GC_size_map數組本身會不斷加載根據需要不斷擴容。


示意

2. 空閑鏈表 - ok_freelist

決定了GRANULE的大小之后,在申請內存時刻首先會從“空閑鏈表”中查看是否有空閑內存塊,如果有則直接返回這塊內存,完成分配,其算法維護了一個數據結構obj_kind:

struct obj_kind {
void **ok_freelist;
struct hblk **ok_reclaim_list;
...
} GC_obj_kinds[3];

GC_obj_kinds[3]對應了3種內存類型,分別是PTRFREE、NORMAL和UNCOLLECTABLE,每種類型都有一個obj_kind結構體信息。

PTRFREE:無指針內存分配,明確的告訴GC,該對象內無任何的指針信息,在GC時候無需查找該對象是否引用了其他對象。

NORMAL:無類型的內存分配,因為無法得到對象的類型元數據,所以在GC時會按照只針對其的方式掃描內存塊,如果通過了指針校驗,就會認為該對象引用了該指針地址指向的對象。

UNCOLLECTABLE:為BOEHM自己分配的內存,這些不需要標記和回收。

每一個obj_kind的結構體都維護了一個ok_freelist的二維指針鏈表用來存放空閑的內存塊。ok_freelist維護了0~127個鏈表索引。而每一個尺寸的freelist就是對應大小的GRANULE池子,其結構示意如圖:


freelist示意

于是,根據要申請的內存大小計算得到GRANULE在freelist的索引,然后去查詢對應索引的freelist,如果存在空閑看空間ok_freelist[index][0],則將其返回并從鏈上移除。


ok_freelist鏈表最初為空,如果ok_freelist中沒有相應的空閑內存塊,則調用GC_allocobj(lg, k)去底層查找可用的內存。

GC_allocobj的核心邏輯是調用GC_new_hblk(gran, kind)去底層內存池獲取內存,并且查看底層內存池中是否分配了空閑的內存塊,如果沒有則通過系統函數例如malloc分配內存給底層內存池,如果內存池有,直接取出一塊返回。GC_new_hblk的代碼邏輯如下:

GC_INNER void GC_new_hblk(size_t gran, int kind)
{
struct hblk *h; /* the new heap block */
GC_bool clear = GC_obj_kinds[kind].ok_init;

/* Allocate a new heap block */
h = GC_allochblk(GRANULES_TO_BYTES(gran), kind, 0);
if (h == 0) return;

/* Build the free list */
GC_obj_kinds[kind].ok_freelist[gran] =
GC_build_fl(h, GRANULES_TO_WORDS(gran), clear,(ptr_t)GC_obj_kinds[kind].ok_freelist[gran]);
}

GC_new_hblk的主要邏輯有2步:

1. 調用GC_allochblk方法進一步獲取內存池中可用的內存塊;

2. 調用GC_build_fl方法,利用內存池中返回的內存塊構建ok_freelist,供上層使用。

3. 核心內存塊鏈表GC_hblkfreelist

底層內存池的實現邏輯和ok_freelist類似,維護了一個空閑內存塊鏈表的指針鏈表GC_hblkfreeelist,但是和ok_freelist不同的是,這個鏈表中的內存塊的基本單位是4KB,也就是一個內存頁(page_size)的大小。GC_hblkfreelist一個有60個元素,每一個元素都是一個鏈表。

4. 內存塊 - hblk、頭信息 - hblkhdr

鏈表中的每一個內存塊都以大小4096(4KB)為一基本單位,一個大小為4096的內存塊被稱為hblk,數據定義如下:

struct hblk {
char hb_body[HBLKSIZE]; //HBLKSIZE=4096
};

每個hblk擁有一個相應的header信息,用來描述這個內存快的情況,數據的定義如下:

//頭部信息
struct hblkhdr {
struct hblk * hb_next; //指向下一個hblk
struct hblk * hb_prev; //指向上一個hblk
struct hblk * hb_block; //對應的hblk
unsigned char hb_obj_kind; //kink類型
unsigned char hb_flags; //標記位
word hb_sz; //如果給上層使用,則表示實際分配的單位,如果空閑,則表示內存塊的大小
word hb_descr;
size_t hb_n_marks;//標記位個數,用于GC
word hb_marks[MARK_BITS_SZ]; //標記為,用于GC
}


5. hblk內存塊查詢

structh blk *GC_allochblk(size_t sz, int kind, unsigned flags/* IGNORE_OFF_PAGE or 0 */)
{
...
//1.計算需要的內存塊大小
blocks_needed = OBJ_SZ_TO_BLOCKS_CHECKED(sz);
start_list = GC_hblk_fl_from_blocks(blocks_needed);

//2.查找精確的hblk內存塊
result = GC_allochblk_nth(sz, kind, flags, start_list, FALSE);
if (0 != result) return result;

may_split = TRUE;
...
if (start_list < UNIQUE_THRESHOLD) {
++start_list;
}
//3.從更大的內存塊鏈表中找
for (; start_list <= split_limit; ++start_list) {
result = GC_allochblk_nth(sz, kind, flags, start_list, may_split);
if (0 != result) break;
}
return result;
}

STATIC int GC_hblk_fl_from_blocks(word blocks_needed)
{
if (blocks_needed <= 32) return blocks_needed;
if (blocks_needed >= 256) return (256-32)/8+32;
return (blocks_needed-32)/8+32;
}

先根據上層需要分配的內存大小計算出需要的內存塊大小,如果申請的大小小于4096字節,則結果是1,對于小對象內存塊的個數就是1。

根據實際需要的內存塊數,判斷并決定從哪一個GC_hblkfreelist鏈表查找,start_list是開始查找的鏈表index,即從GC_hblkfreelist[start_list]開始查找。并不是需要blocks,就一定會從GC_hblkfreelist[blocks]的鏈表中查找,遵循轉換規則(小內存索引是連續的,中內存索引是32+8的步長,大點的內存索引都是60)。

  • 如果blocks_needed小于32,則startlist=blocks_needed,直接去GC_hblkfreelist[blocks_needed]中查找。

  • 如果blocks_needed位于32~256,則startlist=(blocks_needed-32)/8+32,即blocks_needed每增加8個,對應GC_hblkfreelist[index]的index增加1。

  • 如果blocks_needed大于256,則都從GC_hblkfreelist[60]鏈表中查找。

決定從哪個鏈表開始查找之后,首先進行精確查找,如果直接找到,則直接返回找到的內存塊。

如果精準查找失敗,則逐漸增大start_list,從更大的內存塊鏈表中查找。

STATIC struct hblk *GC_allochblk_nth(size_t sz, int kind, unsigned flags, int n, int may_split)
{
struct hblk *hbp;
hdr * hhdr;
struct hblk *thishbp;
hdr * thishdr;/* Header corr. to thishbp */
//計算需要分配的內存塊大小
signed_word size_needed = HBLKSIZE * OBJ_SZ_TO_BLOCKS_CHECKED(sz);


//從鏈表中查找合適的內存塊
for (hbp = GC_hblkfreelist[n];; hbp = hhdr -> hb_next) {
signed_word size_avail;
if (NULL == hbp) return NULL;
//獲取內存塊的header信息
GET_HDR(hbp, hhdr);
//內存塊大小
size_avail = (signed_word)hhdr->hb_sz;
if (size_avail < size_needed) continue;
//可用內存大于需要的分配的大小
if (size_avail != size_needed) {
//要求精準不分割,退出循環,返回空
if (!may_split) continue;
...
if( size_avail >= size_needed ) {
...
//分割內存塊,修改鏈表
hbp = GC_get_first_part(hbp, hhdr, size_needed, n);
break;
}
}
}
if (0 == hbp) return0;
...
//修改header信息
setup_header(hhdr, hbp, sz, kind, flags)
...
return hbp;
}

當分配字節的時候先通過精確查找如果發現有精確內存,則會返回相應的內存塊,如果沒有發現精確內存則會去查找更大的內存塊并進行分割,一半返回使用,一半放到池子里。


拆分示意

如上圖示例,如果要申請1KB,則會先找4KB,如果沒有4KB則去找8KB,找到了8KB就進行兩個4KB的拆分,然后移除8KB出池子,再把拆分過的另一半4KB內存塊加入到池子里:

STATIC struct hblk *GC_get_first_part(struct hblk *h, hdr *hhdr, size_t bytes, int index) {
word total_size = hhdr -> hb_sz;
struct hblk * rest;
hdr * rest_hdr;
//從空閑鏈表刪除
GC_remove_from_fl_at(hhdr, index);
if (total_size == bytes) return h;
//后半部分
rest = (struct hblk *)((word)h + bytes);
//生成header信息
rest_hdr = GC_install_header(rest);
//內存塊大小
rest_hdr -> hb_sz = total_size - bytes;
rest_hdr -> hb_flags = 0;
...
//加入相應的空閑鏈表
GC_add_to_fl(rest, rest_hdr);
}

6. 內存塊分配

如果GC_hblkfreelist空閑鏈表中找不到合適的內存塊,則考慮從系統開辟一段新的內存,并添加到GC_hblkfreelist鏈表中。在GC_expand_hp_inner方法中實現:

GC_INNER GC_bool GC_expand_hp_inner(word n)
{
...
//調用系統方式開辟內存
space = GET_MEM(bytes);
//記錄內存地址和大小
GC_add_to_our_memory((ptr_t)space, bytes);
...
//添加到GC_hblkfreelist鏈表中
GC_add_to_heap(space, bytes);
...
}

GC_add_to_heap方法將創建出來的內存塊加入相應的GC_hblkfreelist鏈表中。同時加入一個全局的存放堆內存信息的數組中。

其中如果發現內存連續的前后內存塊存在且空閑,則合并前后的內存塊,生成一個更大的內存塊。

7. ok_freeList

在GC_new_hblk中調用GC_build_fl方法構建鏈表,就是這個GC系統的緩存池核心數據結構。

//構建ok_freelist[gran]
GC_obj_kinds[kind].ok_freelist[gran] = GC_build_fl(h, GRANULES_TO_WORDS(gran), clear,(ptr_t)GC_obj_kinds[kind].ok_freelist[gran]);

GC_INNER ptr_t GC_build_fl(struct hblk *h, size_t sz, GC_bool clear,
ptr_t list) {
word *p, *prev;
word *last_object;/* points to last object in new hblk*/
...
//構建鏈表
p = (word *)(h -> hb_body) + sz;/* second object in *h*/
prev = (word *)(h -> hb_body);/* One object behind p*/
last_object = (word *)((char *)h + HBLKSIZE);
last_object -= sz;
while ((word)p <= (word)last_object) {
/* current object's link points to last object */
obj_link(p) = (ptr_t)prev;
prev = p;
p += sz;
}
p -= sz;

//拼接之前的鏈表
*(ptr_t *)h = list;
//返回入口地址
return ((ptr_t)p);
}

以4096字節的內存塊劃分為16字節單元的freeList為例,步驟如下:

1. 4096字節按照16字節分配,劃分為256個小內存塊,編號是0~255,將最后一個內存塊(255)作為新鏈表的首節點。

2. 內存地址向前遍歷,建立鏈表,即255的下一個節點是254,尾節點是0。

3. 將尾節點的下一個節點指向原鏈表的首地址。

4. 將新鏈表的首節點地址作為ok_freelist[N],N是上文提到的GRANULE,例如16字節對應1。

重建好的freeList,并將首節點提供給上層使用。

五、Boehm GC:大內存分配

分配大內存對象是指分配的內存大于2048字節。

OBJ_SZ_TO_BLOCKS用于計算需要的hblk內存塊的個數,對于大內存,需要的個數大于等于1。例如需要分配9000字節的內存,則需要3個hblk內存塊,然后調用GC_alloc_large分配內存。

GC_INNER ptr_t GC_alloc_large(size_t lb, int k, unsigned flags)
{
struct hblk * h;
word n_blocks;
ptr_t result;
...
n_blocks = OBJ_SZ_TO_BLOCKS_CHECKED(lb);
...
//分配內存
h = GC_allochblk(lb, k, flags);
...
//分配失敗,系統分配內存塊后繼續嘗試分配
while (0 == h && GC_collect_or_expand(n_blocks, flags != 0, retry)) {
h = GC_allochblk(lb, k, flags);
retry = TRUE;
}
//記錄大內存創建大小
size_t total_bytes = n_blocks * HBLKSIZE;
...
GC_large_allocd_bytes += total_bytes;
...
result = h -> hb_body;
//返回內存地址
return result;
}

大內存分配的內存查找和小對象方式一樣,會不斷增加start_list。從更大的鏈表中查找是否有空閑內存,不同的是,如果查找到了空閑內存不會分裂構建ok_freeList鏈表而是直接返回大內存塊的地址提供使用。

六、Boehm GC:內存分配流程圖


示意

七、額外:SGen GC

Simple Generational Garbage Collection簡稱SGen GC,是相比Boehm GC(貝姆GC)更為先進的一種GC方式。官方Mono在2.8版本中增加了SGen GC,但默認的仍是Boehm GC。3.2版本之后,Mono正式將SGen GC作為默認GC方式。

SGen GC將堆內存分為初生代(Nursery)和舊生代(Old Generation)兩代進行管理,并包含兩個GC過程:Minor GC對初生代進行清理;Major GC對初生代和舊生代同時進行清理。

1. 內存分配策略 - 初代

在SGen GC中,初生代是一塊固定大小的連續內存,默認為4MB,可以通過配置修改。這一點與G1不同,在G1中同一代的Region在物理上是不要求連續的。

為了支持多線程工作,新對象的內存分配依然在每個線程的TLAB中進行,當前每個TLAB均為4KB,有提到可能會在不久后進行優化。而在TLAB內部,內存分配是通過指針碰撞的方式進行的,也就是說,在SGen GC中,初生代內存并沒有進行粒度劃分也沒有分塊管理。

初生代對象跟隨Minor GC和Major GC進行回收。

2. 內存分配策略 - 舊代

在SGen GC中,舊生代內存劃分方式可以概括為:

Section(1MB) → Block(16KB)→ Page(4KB)→ Slot(不同粒度)

在使用內存時,按照上述鏈條依次向下拆分,與貝姆GC相同,同一個Block中的Page也只能拆分成相同粒度的Slot。

雖然在初生代中并沒有劃分內存粒度,但是當對象從初生代轉移到舊生代時會找到對應粒度的Slot進行存儲釋放對象時,對應的Slot也會返還給空閑鏈表(類似貝姆GC中的ok_freeList),并在某一級結構完全清空時依次向上一級返還

舊生代內存最終是通過一個GCMemSection結構的鏈表進行管理的。

3. 內存分配策略 - 大對象

超過8KB的對象均被視為大對象,大對象通過單獨的LOSSection結構進行管理。而大對象的內存管理又分為兩種情況:

  • 不超過1MB的,仍然存儲在Mono自己的托管堆上,清理后返還給托管堆;

  • 超過1MB的,直接從操作系統申請內存,清理后內存也同樣返還給操作系統。

4. 內存分配策略 - 固定內存對象

有一些對象被顯式或隱式地標記為了固定內存的對象,這些對象在初始時依然被分配在初生代中,但不會被GC過程移動位置。

  • 顯式:用戶顯式聲明的,比如通過fixed關鍵字進行修飾;

  • 隱式:在GC開始時,所有寄存器和ROOT中直接指向的對象都視為固定內存對象。

文末,再次感謝Jamin 的分享, 作者主頁:https://www.zhihu.com/people/liang-zhi-ming-70, 如果您有任何獨到的見解或者發現也歡迎聯系我們,一起探討。(QQ群: 793972859 )。

近期精彩回顧

特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相關推薦
熱點推薦
注意!蘋果通知 iOS 26.4 系統將強制開啟此功能

注意!蘋果通知 iOS 26.4 系統將強制開啟此功能

XCiOS俱樂部
2026-03-21 09:54:15
難怪汪峰要分手!面容憔悴身材走樣,47歲的章子怡讓人難以辨認

難怪汪峰要分手!面容憔悴身材走樣,47歲的章子怡讓人難以辨認

小椰的奶奶
2026-03-22 08:58:17
“最強編程模型”基于Kimi K2.5打造?連馬斯克都確認了,月之暗面發文回應

“最強編程模型”基于Kimi K2.5打造?連馬斯克都確認了,月之暗面發文回應

澎湃新聞
2026-03-21 15:20:26
馬上停止吃這種瓜,有毒、傷肝腎!一盤上桌,全家遭殃!年年出事

馬上停止吃這種瓜,有毒、傷肝腎!一盤上桌,全家遭殃!年年出事

醫學科普匯
2026-03-21 21:10:04
一針見血!黃健翔點評日本女足奪冠:四大核心邏輯,道出強大根源

一針見血!黃健翔點評日本女足奪冠:四大核心邏輯,道出強大根源

田先生籃球
2026-03-22 06:46:27
他接受紀律審查和監察調查

他接受紀律審查和監察調查

錫望
2026-03-21 22:23:21
正式退役!整整28年啊,終于可以退役了,神射手終于圓夢了

正式退役!整整28年啊,終于可以退役了,神射手終于圓夢了

球童無忌
2026-03-21 00:47:48
他從銀行辭職,成為上海輔警

他從銀行辭職,成為上海輔警

風露清青
2026-03-21 17:21:47
太脆了吧!申花神鋒剛復出又受傷下場了 阿蘇埃能回歸倒計時嗎

太脆了吧!申花神鋒剛復出又受傷下場了 阿蘇埃能回歸倒計時嗎

80后體育大蜀黍
2026-03-21 23:32:40
舒淇雷軍同框太戳心!年過半百仍藏社恐溫柔,這份純粹太難得

舒淇雷軍同框太戳心!年過半百仍藏社恐溫柔,這份純粹太難得

阿廢冷眼觀察所
2026-03-21 20:38:59
《斯巴達克斯》女神排行榜,前三個太頂了

《斯巴達克斯》女神排行榜,前三個太頂了

來看美劇
2026-03-17 21:16:08
有多少中年男人,去四川切過咪咪?

有多少中年男人,去四川切過咪咪?

不相及研究所
2026-03-21 22:14:55
特朗普警告:若48小時內不開放霍爾木茲海峽,將轟炸伊朗的發電站

特朗普警告:若48小時內不開放霍爾木茲海峽,將轟炸伊朗的發電站

AI商業論
2026-03-22 08:51:35
客戰云南玉昆0比4慘敗:泰山4大死穴曝光,夏窗調整方向明確

客戰云南玉昆0比4慘敗:泰山4大死穴曝光,夏窗調整方向明確

體壇小鵬
2026-03-22 08:59:50
特朗普,被“背叛”了

特朗普,被“背叛”了

中國新聞周刊
2026-03-21 15:26:15
日本的幸福感為何那么低

日本的幸福感為何那么低

徐靜波靜說日本
2026-03-22 07:16:00
金價跳水,菜百店里擠滿人,有人剛賣完金又搶著買回來

金價跳水,菜百店里擠滿人,有人剛賣完金又搶著買回來

趣味萌寵的日常
2026-03-21 20:32:15
人到老年才知道,增加骨密度最好的運動,竟然不是跑步和走路

人到老年才知道,增加骨密度最好的運動,竟然不是跑步和走路

墜入二次元的海洋
2026-03-17 10:25:30
教育大局已定:2026年初中考高中將迎來3大變化,家長要早作準備

教育大局已定:2026年初中考高中將迎來3大變化,家長要早作準備

夜深愛雜談
2026-03-18 21:58:01
2024年葉誠塵被注射死刑,警方恢復大量聊天內容,發現她有一怪癖

2024年葉誠塵被注射死刑,警方恢復大量聊天內容,發現她有一怪癖

瞻史
2026-03-19 21:06:35
2026-03-22 09:36:49
侑虎科技UWA incentive-icons
侑虎科技UWA
游戲/VR性能優化平臺
1558文章數 986關注度
往期回顧 全部

科技要聞

庫克在華這四天,一場既定的市場秀

頭條要聞

男子在壺口瀑布外拍視頻喊"門口要錢"被投訴 景區回應

頭條要聞

男子在壺口瀑布外拍視頻喊"門口要錢"被投訴 景區回應

體育要聞

誰在決定字母哥未來?

娛樂要聞

田栩寧終于涼了?出軌風波影響惡劣

財經要聞

通脹警報拉響,加息潮要來了?

汽車要聞

小鵬汽車2025年Q4盈利凈賺3.8億 全年營收767億

態度原創

旅游
健康
教育
親子
數碼

旅游要聞

千畝杏林迎客來,濟南南山柳埠街道解鎖春日度假新玩法

轉頭就暈的耳石癥,能開車上班嗎?

教育要聞

校長講好五類故事,凝聚辦學人心

親子要聞

“鋅”是聰明根!春天孩子多吃高鋅菜,腦子靈、記性好、個頭猛長

數碼要聞

觸控屏+更好看+更強悍,坐等今年新MacBook Pro

無障礙瀏覽 進入關懷版