<ruby id="9ue20"></ruby>

  1. 
    

      国产午夜福利免费入口,国产日韩综合av在线,精品久久人人妻人人做精品,蜜臀av一区二区三区精品,亚洲欧美中文日韩在线v日本,人妻av中文字幕无码专区 ,亚洲精品国产av一区二区,久久精品国产清自在天天线
      網(wǎng)易首頁 > 網(wǎng)易號 > 正文 申請入駐

      Unity IL2CPP的GC原理

      0
      分享至


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

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

      作者主頁:

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

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

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

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

      1. 什么時候觸發(fā)垃圾回收

      有三個操作會觸發(fā)垃圾回收:

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

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

      • GC被代碼強制執(zhí)行。

      2. GC操作帶來的問題

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

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

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

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

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

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

      二、Unity托管堆

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

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

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


      Unity官方文檔圖

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

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

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


      內存空隙

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

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


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

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

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

      2. Unity托管堆的問題

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

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

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

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

      三、Unity的GC機制 -- Boehm GC

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

      1. IL2CPP - Boehm GC

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

      摘錄一段定義:

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

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


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

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

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

      2. Boehm GC算法思路

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

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

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

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

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

      3. 漸進式GC

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

      注: Unity在高版本已經(jīng)默認是漸進式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通過平臺相關接口來向操作系統(tǒng)申請內存。為了提高申請的效率,每次批量申請4KB的倍數(shù)大小。

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

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

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

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

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

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

      1. 粒度對齊

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

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

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

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


      示意

      2. 空閑鏈表 - ok_freelist

      決定了GRANULE的大小之后,在申請內存時刻首先會從“空閑鏈表”中查看是否有空閑內存塊,如果有則直接返回這塊內存,完成分配,其算法維護了一個數(shù)據(jù)結構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:無類型的內存分配,因為無法得到對象的類型元數(shù)據(jù),所以在GC時會按照只針對其的方式掃描內存塊,如果通過了指針校驗,就會認為該對象引用了該指針地址指向的對象。

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

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


      freelist示意

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


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

      GC_allocobj的核心邏輯是調用GC_new_hblk(gran, kind)去底層內存池獲取內存,并且查看底層內存池中是否分配了空閑的內存塊,如果沒有則通過系統(tǒng)函數(shù)例如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

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

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

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

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

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

      //頭部信息
      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;//標記位個數(shù),用于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;
      }

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

      根據(jù)實際需要的內存塊數(shù),判斷并決定從哪一個GC_hblkfreelist鏈表查找,start_list是開始查找的鏈表index,即從GC_hblkfreelist[start_list]開始查找。并不是需要blocks,就一定會從GC_hblkfreelist[blocks]的鏈表中查找,遵循轉換規(guī)則(小內存索引是連續(xù)的,中內存索引是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) {
      //要求精準不分割,退出循環(huán),返回空
      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;
      }

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


      拆分示意

      如上圖示例,如果要申請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空閑鏈表中找不到合適的內存塊,則考慮從系統(tǒng)開辟一段新的內存,并添加到GC_hblkfreelist鏈表中。在GC_expand_hp_inner方法中實現(xiàn):

      GC_INNER GC_bool GC_expand_hp_inner(word n)
      {
      ...
      //調用系統(tǒng)方式開辟內存
      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方法將創(chuàng)建出來的內存塊加入相應的GC_hblkfreelist鏈表中。同時加入一個全局的存放堆內存信息的數(shù)組中。

      其中如果發(fā)現(xiàn)內存連續(xù)的前后內存塊存在且空閑,則合并前后的內存塊,生成一個更大的內存塊。

      7. ok_freeList

      在GC_new_hblk中調用GC_build_fl方法構建鏈表,就是這個GC系統(tǒng)的緩存池核心數(shù)據(jù)結構。

      //構建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字節(jié)的內存塊劃分為16字節(jié)單元的freeList為例,步驟如下:

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

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

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

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

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

      五、Boehm GC:大內存分配

      分配大內存對象是指分配的內存大于2048字節(jié)。

      OBJ_SZ_TO_BLOCKS用于計算需要的hblk內存塊的個數(shù),對于大內存,需要的個數(shù)大于等于1。例如需要分配9000字節(jié)的內存,則需要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);
      ...
      //分配失敗,系統(tǒng)分配內存塊后繼續(xù)嘗試分配
      while (0 == h && GC_collect_or_expand(n_blocks, flags != 0, retry)) {
      h = GC_allochblk(lb, k, flags);
      retry = TRUE;
      }
      //記錄大內存創(chuàng)建大小
      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中,初生代是一塊固定大小的連續(xù)內存,默認為4MB,可以通過配置修改。這一點與G1不同,在G1中同一代的Region在物理上是不要求連續(xù)的。

      為了支持多線程工作,新對象的內存分配依然在每個線程的TLAB中進行,當前每個TLAB均為4KB,有提到可能會在不久后進行優(yōu)化。而在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的,直接從操作系統(tǒng)申請內存,清理后內存也同樣返還給操作系統(tǒng)。

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

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

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

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

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

      近期精彩回顧

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

      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.

      相關推薦
      熱點推薦
      以色列剛警告中國,特朗普說實話:有一件事,全球只有中美能做到

      以色列剛警告中國,特朗普說實話:有一件事,全球只有中美能做到

      浪子阿邴聊體育
      2026-05-13 05:50:25
      三年退款2700次!一哥們把「僅退款」當班上,把自己上進了局子

      三年退款2700次!一哥們把「僅退款」當班上,把自己上進了局子

      雷科技
      2026-05-12 22:06:26
      若你的小腳趾有2瓣趾甲,或許不是純正漢人,而是這一種族的后裔

      若你的小腳趾有2瓣趾甲,或許不是純正漢人,而是這一種族的后裔

      抽象派大師
      2026-05-12 20:25:28
      育碧"曠世神作"不如奶茶貴!一折賤賣吃不了虧

      育碧"曠世神作"不如奶茶貴!一折賤賣吃不了虧

      游民星空
      2026-05-13 22:23:17
      2026年“端午節(jié)放假通知”:1個好消息、3個壞消息、2個要注意

      2026年“端午節(jié)放假通知”:1個好消息、3個壞消息、2個要注意

      娛樂圈見解說
      2026-05-14 01:10:07
      一廁所配圖露骨:男廁“手握香蕉剝開”,女廁“手摳切開的西柚”

      一廁所配圖露骨:男廁“手握香蕉剝開”,女廁“手摳切開的西柚”

      川渝視覺
      2026-05-11 21:34:56
      美國對中國統(tǒng)一下達新結論:大陸只要按兵不動,越晚統(tǒng)一代價越小

      美國對中國統(tǒng)一下達新結論:大陸只要按兵不動,越晚統(tǒng)一代價越小

      華史談
      2026-05-13 08:49:26
      特朗普訪華前第一劍,指向日本!日本不等了,派人訪華

      特朗普訪華前第一劍,指向日本!日本不等了,派人訪華

      被忽略的美好
      2026-05-14 05:05:12
      她只是安安靜靜地待著,和自己的曲線和解

      她只是安安靜靜地待著,和自己的曲線和解

      飛娛日記
      2026-05-13 07:24:53
      領導的網(wǎng)名叫上善若水,我該叫什么才能壓他一頭?

      領導的網(wǎng)名叫上善若水,我該叫什么才能壓他一頭?

      摩登人類
      2026-05-11 19:43:02
      菲律賓參議院大樓傳出槍聲 菲律賓總統(tǒng)稱未下達逮捕德拉羅薩的指令

      菲律賓參議院大樓傳出槍聲 菲律賓總統(tǒng)稱未下達逮捕德拉羅薩的指令

      新華社
      2026-05-14 00:10:45
      長得帥在生活中有多大優(yōu)勢?網(wǎng)友:特別舍得給老公花錢

      長得帥在生活中有多大優(yōu)勢?網(wǎng)友:特別舍得給老公花錢

      另子維愛讀史
      2026-05-13 22:40:33
      岳父是高管,岳母開公司,娶了乒乓冠軍的許昕,在上海兒女雙全

      岳父是高管,岳母開公司,娶了乒乓冠軍的許昕,在上海兒女雙全

      鄉(xiāng)野小珥
      2026-05-13 14:27:37
      印菲湊齊75%鎳資源后,才發(fā)現(xiàn)王傳福多年前已做好應對準備

      印菲湊齊75%鎳資源后,才發(fā)現(xiàn)王傳福多年前已做好應對準備

      阿傖說事
      2026-05-13 11:27:53
      央視硬氣,拒絕5折轉播費!FIFA官網(wǎng)刪中文模式,給央視下馬威

      央視硬氣,拒絕5折轉播費!FIFA官網(wǎng)刪中文模式,給央視下馬威

      開成運動會
      2026-05-13 22:49:59
      國米2比0拉齊奧奪意大利杯,勞塔羅破門助藍黑軍團國內雙冠

      國米2比0拉齊奧奪意大利杯,勞塔羅破門助藍黑軍團國內雙冠

      賽場名場面
      2026-05-14 06:18:58
      四川一聾啞老人賣菜籽970斤被稱成596斤,鄰居察覺后拿自家秤幫忙二次稱重,商販最后仍少給20元,家屬:“我們今天買了東西感謝鄰居”

      四川一聾啞老人賣菜籽970斤被稱成596斤,鄰居察覺后拿自家秤幫忙二次稱重,商販最后仍少給20元,家屬:“我們今天買了東西感謝鄰居”

      臺州交通廣播
      2026-05-13 07:00:37
      時隔5個月!孩子生父塵埃落定,奚美娟迎來翻盤,周野芒早有預言

      時隔5個月!孩子生父塵埃落定,奚美娟迎來翻盤,周野芒早有預言

      情感大頭說說
      2026-05-13 14:37:29
      英如鏑直播怒斥巴圖:改名宋驍,半年不回私信想當大伯?

      英如鏑直播怒斥巴圖:改名宋驍,半年不回私信想當大伯?

      陳意小可愛
      2026-05-12 09:28:38
      87億打水漂!當初搶的時候多囂張,現(xiàn)在就多狼狽:荷蘭大臣哭暈

      87億打水漂!當初搶的時候多囂張,現(xiàn)在就多狼狽:荷蘭大臣哭暈

      觀史搜尋著
      2026-05-13 12:50:11
      2026-05-14 07:12:49
      侑虎科技UWA incentive-icons
      侑虎科技UWA
      游戲/VR性能優(yōu)化平臺
      1575文章數(shù) 987關注度
      往期回顧 全部

      科技要聞

      阿里年營收首破萬億,AI終于不再是畫大餅

      頭條要聞

      女子閃婚獲千萬房產(chǎn)99%份額閃離后起訴分割 法院判了

      頭條要聞

      女子閃婚獲千萬房產(chǎn)99%份額閃離后起訴分割 法院判了

      體育要聞

      14年半,74萬,何冰嬌沒選那條更安穩(wěn)的路

      娛樂要聞

      白鹿掉20萬粉,網(wǎng)友為李晨鳴不平

      財經(jīng)要聞

      美國總統(tǒng)特朗普抵達北京

      汽車要聞

      C級純電轎跑 吉利銀河"TT"申報圖來了

      態(tài)度原創(chuàng)

      房產(chǎn)
      數(shù)碼
      親子
      家居
      教育

      房產(chǎn)要聞

      卷瘋了!最低殺到7字頭!手握30萬,海口樓市橫著走!

      數(shù)碼要聞

      徠芬智能卷發(fā)棒Styler發(fā)布,499元

      親子要聞

      農(nóng)村童趣日常,樹上果糖拌奶吃,一口下去太解饞

      家居要聞

      內在自敘,無域有方

      教育要聞

      家長就讀,孩子免費修大學學分的社區(qū)大學?

      無障礙瀏覽 進入關懷版 主站蜘蛛池模板: 无码国产偷倩在线播放| 日韩在线精品在线观看| 日韩av熟女人妻一区二| 久久99久久99精品免视看国产成人| 久久精品国产蜜臀av| 最新国产精品拍自在线观看 | 欧美一区| 久久精品国产99久久丝袜| 国产无吗一区二区三区在线欢| 国产精品综合一区二区三区| 国产偷人妻精品一区二区在线| 国产成人美女视频网站| 老色鬼在线精品视频| 亚洲成人黄色av| 无码www毛色一区二区| 蜜桃久久精品成人无码av| 黄桃av无码免费一区二区三区| 18禁国产美女白浆在线| 国产在线精品成人一区二区| 黑人好猛厉害爽受不了好大撑| 一本无码中文字幕| ww无码| 国产91视频免费观看| 久久精品国产亚洲AV瑜伽 | 高清国产亚洲精品自在久久| 男女做aj视频免费的网站| 日韩大片高清播放器| 成年女人午夜毛片免费视频| 顺昌县| 国产精品多P对白交换绿帽| 丁香六月婷婷综合激情欧美| 精品国偷自产在线视频99| 成人3D动漫一区二区三区| 天天狠天天透天天伊人| 久久久亚洲国产精品主播| 五月婷丁香| 亚洲日韩乱码中文无码蜜桃臀| 国产成人久久精品二区三| 91资源总站| 最新中文字幕av无码专区| 99r久视频精品视频在线|