日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

CoreCLR文档翻译 - GC的设计

發(fā)布時(shí)間:2023/12/4 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 CoreCLR文档翻译 - GC的设计 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

此文檔來源于CoreCLR的BOTR(The Book of the Runtime),?點(diǎn)擊打開原文
一切著作權(quán)歸微軟公司所有

GC的設(shè)計(jì)

作者: Maoni Stephens (@maoni0) - 2015

提示: 推薦看?The Garbage Collection Handbook?這本書學(xué)習(xí)更多關(guān)于GC的知識(shí) (在文章底部的鏈接中)

組件結(jié)構(gòu)

在GC中有兩個(gè)主要的組件, 一個(gè)是分配器(Allocator), 另一個(gè)是收集器(Collector).
分配器負(fù)責(zé)獲取更多的內(nèi)存并且在適當(dāng)?shù)臅r(shí)機(jī)觸發(fā)收集器.
收集器負(fù)責(zé)回收垃圾和不再被程序使用的對(duì)象內(nèi)存.

此外還有一些途徑可以觸發(fā)收集器, 例如手動(dòng)調(diào)用GC.Collect函數(shù)或析構(gòu)線程(Finalizer Thread)收到一個(gè)內(nèi)存不足的異步通知(由收集器發(fā)送).

分配器的設(shè)計(jì)

分配器由運(yùn)行引擎(Execution Engine (EE))調(diào)用, 調(diào)用時(shí)會(huì)帶有以下的信息:

  • 需要分配的大小

  • 線程專用的分配上下文(Allocation Context)

  • 標(biāo)記, 如對(duì)象是否有析構(gòu)函數(shù)

GC不會(huì)根據(jù)對(duì)象類型的不同做出特殊處理, 它會(huì)從運(yùn)行引擎獲取對(duì)象的大小, 根據(jù)對(duì)象的大小把對(duì)象分為兩類:

  • 小對(duì)象 (小于 85,000字節(jié))

  • 大對(duì)象 (大于或等于 85,000字節(jié))

原則上小對(duì)象和大對(duì)象都可以用同樣的方式處理, 但因?yàn)閴嚎s(Compacting)大對(duì)象的代價(jià)會(huì)比較昂貴所以GC會(huì)區(qū)分對(duì)待.

當(dāng)GC把一段內(nèi)存交給分配器時(shí), 它會(huì)參照分配上下文(Allocation Context).
分配上下文的大小取決于分配單位(Allocation Quantum)的定義.

  • 分配上下文是在堆段(Heap Segment)中專門給指定線程使用的小區(qū)域, 在單核計(jì)算機(jī)(指單邏輯核心)中只會(huì)使用一個(gè)上下文, 即第0世代(Generation 0)分配上下文.

  • 分配單位是分配器每次申請(qǐng)的內(nèi)存大小, 用于在分配上下文中給對(duì)象分配內(nèi)存. 分配單位通常為8K且受管理對(duì)象(Managed Object)的大小通常為32字節(jié), 使得一個(gè)分配單位可以用于多個(gè)對(duì)象的分配 (譯者注: 原理和內(nèi)存池相同)

大對(duì)象不會(huì)使用分配上下文和分配單位, 因?yàn)橐粋€(gè)大對(duì)象本身可以大于這些小區(qū)域.
并且使用這些區(qū)域帶來的好處(會(huì)在下面討論)僅僅受限于小對(duì)象.
大對(duì)象會(huì)直接在堆段(Heap Segment)中分配.

分配器的設(shè)計(jì)要求實(shí)現(xiàn):

  • 在適當(dāng)時(shí)機(jī)觸發(fā)GC:?如果超過了分批預(yù)算(由收集器設(shè)置的閾值)或分配器不能在指定的堆段上分配時(shí)將會(huì)觸發(fā)GC, 分配預(yù)算(Allocation Budget)和受管理的段(Managed Segments)的細(xì)節(jié)將在下面討論.

  • 保持對(duì)象位置:?如果多個(gè)對(duì)象在同一個(gè)堆段中分配, 它們的虛擬內(nèi)存地址也會(huì)鄰接.

  • 高效的使用緩存:?分配器每次都會(huì)按?分配單位?申請(qǐng)內(nèi)存, 而不是按每個(gè)對(duì)象的大小. 在申請(qǐng)內(nèi)存后它會(huì)對(duì)足夠激活cpu緩存的內(nèi)存大小進(jìn)行清0, 因?yàn)樵诖酥髮?duì)象會(huì)從這塊內(nèi)存中分配(所以性能會(huì)得到提升). 分配單位通常為8K.

  • 高效的避免線程鎖:?因?yàn)榉峙渖舷挛暮途€程的綁定, 可以保證每個(gè)分配單位的內(nèi)存只有對(duì)應(yīng)一個(gè)線程可以操作. 因此只要當(dāng)前的分配上下文沒有用完, 給對(duì)象分配內(nèi)存時(shí)就不需要線程鎖.

  • 內(nèi)存完整性:?GC總會(huì)把新分配的對(duì)象的內(nèi)存清0, 從而防止對(duì)象指向隨機(jī)的內(nèi)容(未定義的內(nèi)容).

  • 保證堆可爬取(Crawlable):?分配器會(huì)在每個(gè)分配單位即將用完之際新建一個(gè)自由對(duì)象(Free Object)填充, 例如一個(gè)分配單位剩余30個(gè)字節(jié)并且下一個(gè)要分配的對(duì)象需要40個(gè)字節(jié), 則分配器會(huì)新建一個(gè)30個(gè)字節(jié)的自由對(duì)象填充原來的分配單位并重新獲取一個(gè)新的分配單位

分配器的API

Object* GCHeap::Alloc(size_t size, DWORD flags);Object* GCHeap::Alloc(alloc_context* acontext, size_t size, DWORD flags);

以上的函數(shù)可以用于分配小對(duì)象和大對(duì)象.
另外還有一個(gè)函數(shù)用于強(qiáng)制從大對(duì)象的堆(LOH: Large Object Heap)中分配內(nèi)存:

Object* GCHeap::AllocLHeap(size_t size, DWORD flags);

收集器的設(shè)計(jì)

GC的目標(biāo)

GC致力于高效的內(nèi)存管理, 只要求程序員付出很小的努力
高效指的是:

  • GC應(yīng)該足夠頻繁的發(fā)生, 以避免堆中有大量(根據(jù)比例和絕對(duì)值)未使用但已分配的對(duì)象(垃圾)和多余的內(nèi)存.

  • GC應(yīng)該盡可能不頻繁的發(fā)生, 以避免過多的消耗cpu時(shí)間, 即便頻繁發(fā)生可以讓程序占用更小的內(nèi)存.

  • GC應(yīng)該是生產(chǎn)性的, 如果一次GC只回收了少量的內(nèi)存那么這次GC和它消耗的cpu周期被浪費(fèi)了.

  • 每次GC應(yīng)該足夠快, 許多場(chǎng)景會(huì)要求低延遲.

  • 程序員們不需要為了優(yōu)化內(nèi)存的利用對(duì)GC了解太多(取決于他們的工作).
    – GC應(yīng)該調(diào)整自身以適應(yīng)不同的內(nèi)存使用模式.

受管理的堆(Managed Heap)的邏輯表現(xiàn)

CLR GC把對(duì)象分成了不同的世代, 當(dāng)?shù)?N?世代的垃圾被回收后, 生存的對(duì)象會(huì)被標(biāo)記為第?N+1?世代, 這個(gè)過程被稱為升級(jí)(Promotion).
還有一些例外的情況當(dāng)我們決定是否降級(jí)或不升級(jí).

對(duì)于小對(duì)象的堆會(huì)分為3個(gè)世代: 第0世代(gen0), 第1世代(gen1)和第2世代(gen2).
對(duì)于大對(duì)象只有1個(gè)世代: 第3世代(gen3).
第0世代和第1世代被稱為短暫(對(duì)象的生命周期短)的世代.

對(duì)于小對(duì)象的堆, 世代中的數(shù)字代表了年齡, 第0世代是最年輕的世代.
但不代表第0世代中的對(duì)象一定比第1世代和第2世代的對(duì)象年輕,?下面將會(huì)說明那些例外.
收集一個(gè)世代中的垃圾同時(shí)也會(huì)收集所有比它年輕的世代的垃圾.

原則上大對(duì)象可以使用和小對(duì)象一樣的處理方式, 但是壓縮大對(duì)象的代價(jià)會(huì)非常的昂貴, 因此大對(duì)象和小對(duì)象會(huì)受到不同的對(duì)待.
因?yàn)樾阅苌系脑? 大對(duì)象只使用了一個(gè)世代(第3世代)并且這個(gè)世代會(huì)和第2世代一起回收垃圾.
因?yàn)榈?世代和第3世代可以很大, 需要和短暫的世代(第0世代和第1世代)在開銷上劃出邊界.

為對(duì)象分配內(nèi)存時(shí)總會(huì)從最年輕的世代分配 - 對(duì)于小對(duì)象總會(huì)從第0世代分配, 對(duì)于大對(duì)象總會(huì)從第3世代分配(因?yàn)橹挥幸粋€(gè)世代).

受管理的堆(Managed Heap)的物理表現(xiàn)

受管理的堆(Managed Heap)是一個(gè)包含了受管理的堆段(Managed Heap Segments)的集合.
受管理的堆段是從系統(tǒng)內(nèi)核申請(qǐng)得到的一塊連續(xù)的內(nèi)存空間. (譯者注: 即malloc/brk申請(qǐng)得到的空間)
用于區(qū)別小對(duì)象和大對(duì)象, 堆段又分為小對(duì)象堆段和大對(duì)象堆段.
每個(gè)堆中的堆段都是相互鏈接在一起的, 至少會(huì)有1個(gè)小對(duì)象堆段和1個(gè)大對(duì)象堆段 - 它們會(huì)在加載CLR時(shí)預(yù)留.

每個(gè)小對(duì)象的堆中只有一個(gè)短暫的堆段(Ephemeral Segment)用于存放短暫世代(第0世代和第1世代)的對(duì)象, 但也有可能包含第2世代的對(duì)象.
其他額外的堆段(0或1或更多個(gè))中只會(huì)存放第2世代的對(duì)象.

每個(gè)大對(duì)象的堆中有一個(gè)或更多個(gè)堆段.

堆段中的空間會(huì)從較低的地址向較高的地址消耗, 即地址更小的對(duì)象比地址更大的對(duì)象更老.?這里也有一些例外將在下面說明.

堆段會(huì)在需要時(shí)向系統(tǒng)申請(qǐng), 并且在不包含任何生存的對(duì)象時(shí)刪除.
但是初始的堆段(加載時(shí)預(yù)留的)會(huì)一直保留.

每個(gè)堆中每次只會(huì)處理一段(而不是全部), 如在回收小對(duì)象和分配大對(duì)象時(shí).
這樣的設(shè)計(jì)提供了更好的性能, 因?yàn)榇髮?duì)象只會(huì)在第2世代回收時(shí)一同回收(代價(jià)相當(dāng)昂貴).

堆段會(huì)按它們的申請(qǐng)順序鏈接在一起, 鏈中的最后一個(gè)堆段一定是短暫的堆段(Ephemeral Segment).
回收的堆段(不包含任何生存的對(duì)象)不一定會(huì)被刪除, 也可能被作為一個(gè)新的短暫的堆段, 這種機(jī)制僅在小對(duì)象的堆中實(shí)現(xiàn).
每次分配大對(duì)象都會(huì)考慮整個(gè)大對(duì)象的堆, 而小對(duì)象僅僅考慮短暫的堆段.

分配預(yù)算(Allocation Budget)

分配預(yù)算是一個(gè)關(guān)聯(lián)于每個(gè)世代的邏輯概念, 當(dāng)世代的大小達(dá)到了指定的限制則會(huì)觸發(fā)GC.
每個(gè)世代的預(yù)算值屬性基本取決于該世代的對(duì)象的生存率, 如果生存率較高, 那么這個(gè)限制會(huì)調(diào)大使得下次對(duì)該世代的GC會(huì)得到一個(gè)更好的回收率.

判斷需要回收哪個(gè)世代的垃圾

當(dāng)GC被觸發(fā)時(shí), GC首先需要確定回收哪個(gè)世代.
除了分配預(yù)算外, 還有這些因素需要考慮:

  • 世代的碎片化程度 – 如果這個(gè)世代的碎片化程度比較高, 則收集這個(gè)世代將會(huì)得到更好的效果

  • 如果系統(tǒng)內(nèi)存占用比較高, 并且每次可以回收到一定的內(nèi)存, GC可能會(huì)更積極的去回收.
    這對(duì)于防止系統(tǒng)內(nèi)存分頁(yè)(把多出的內(nèi)存數(shù)據(jù)轉(zhuǎn)移到硬盤)很重要.

  • 如果短暫的堆段的空間已經(jīng)用完, GC可能會(huì)更積極的去回收以防止申請(qǐng)一個(gè)新的堆段.

GC的工作流程

標(biāo)記階段 (Mark phase)

標(biāo)記階段的目標(biāo)是尋找所有存活的對(duì)象.

多世代收集器的好處是每次只需要去看堆中最近的對(duì)象, 而不需要去看歷史生成的所有對(duì)象.
當(dāng)收集短暫世代(第0世代和第1世代)中的對(duì)象時(shí), GC需要尋找這些世代中所有存活的對(duì)象,
運(yùn)行引擎(EE)使用中的對(duì)象會(huì)標(biāo)記為存活, 此外被其他對(duì)象(更老世代的對(duì)象)引用的對(duì)象也會(huì)標(biāo)記為存活.

GC在標(biāo)記更老的世代中的對(duì)象時(shí)會(huì)使用卡片(Cards),
JIT的幫助類會(huì)在賦值操作時(shí)設(shè)置卡片, 如果JIT的幫助類看到一個(gè)對(duì)象在短暫的范圍中它會(huì)設(shè)置一個(gè)包含了源位置的卡片的字節(jié).
在短暫世代的回收中, GC可以只看堆中其余的部分設(shè)置的卡片來找到它們對(duì)應(yīng)的對(duì)象.
(譯者注: 卡片表用于標(biāo)記跨代引用,例如只掃代0的時(shí)候如果代1引用了代0的對(duì)象也要掃卡片表中代1的部分對(duì)象)

計(jì)劃階段 (Plan phase)

計(jì)劃階段會(huì)做一個(gè)比較來決定使用壓縮(Compaction)還是清掃(Sweeps).
如果壓縮效果更好則GC會(huì)開始實(shí)際的壓縮, 否則GC會(huì)開始清掃.

重定位階段 (Relocate phase)

如果GC決定要壓縮, 那就要移動(dòng)現(xiàn)有的對(duì)象, 指向這些對(duì)象的引用都需要被更新.
重定位階段需要找出指向回收世代中的對(duì)象的所有引用.
相反, 標(biāo)記階段僅會(huì)參考存活的對(duì)象因此不需要考慮弱引用(Weak References).

壓縮階段 (Compact phase)

這個(gè)階段的目標(biāo)非常直接, 因?yàn)橛?jì)劃階段已經(jīng)計(jì)算了對(duì)象應(yīng)該移動(dòng)到的新地址, 壓縮階段會(huì)復(fù)制對(duì)象到這些新地址中.

清掃階段 (Sweep phase)

清掃階段會(huì)尋找夾在存活對(duì)象中的空余空間(死對(duì)象), 并在這些空間里創(chuàng)建一些自由對(duì)象.
相鄰的死對(duì)象會(huì)被合并成一個(gè)自由對(duì)象.
創(chuàng)建的自由對(duì)象會(huì)放到?自由對(duì)象列表(freelist)?里.

代碼流程

縮寫: (譯者注: 以下不會(huì)使用這些縮寫)

  • WKS GC:?工作站GC.

  • SRV GC:?服務(wù)器GC

各個(gè)模式的舉動(dòng)

工作站GC - 不啟用并發(fā)式GC

  • 用戶線程超過了分配預(yù)算并觸發(fā)了GC.

  • GC調(diào)用SuspendEE函數(shù)來停止所有受管理的線程(Managed Threads).

  • GC決定需要回收哪個(gè)世代.

  • 運(yùn)行標(biāo)記階段.

  • 運(yùn)行計(jì)劃階段決定是用壓縮還是用清掃.

  • 如果決定壓縮則運(yùn)行重定位階段和壓縮階段, 否則運(yùn)行清掃階段.

  • GC調(diào)用RestartEE函數(shù)來恢復(fù)受管理的線程.

  • 用戶線程恢復(fù)運(yùn)行.

  • 工作站GC - 啟用并發(fā)式GC

    這里說明了后臺(tái)GC如何運(yùn)作.

  • 用戶線程超過了分配預(yù)算并觸發(fā)了GC.

  • GC調(diào)用SuspendEE函數(shù)來停止所有受管理的線程(Managed Threads).

  • GC決定是否使用后臺(tái)GC.

  • 如果使用后臺(tái)GC, 則后臺(tái)GC線程會(huì)被喚醒并進(jìn)行回收工作. 后臺(tái)GC線程會(huì)調(diào)用RestartEE喚醒受管理的線程.

  • 受管理的線程繼續(xù)運(yùn)行, 同時(shí)后臺(tái)GC線程也繼續(xù)它的工作.

  • 用戶線程可能又一次的超過了分配預(yù)算并觸發(fā)了一個(gè)短暫的GC(我們稱為前臺(tái)GC), 流程和"工作站GC - 不啟用并發(fā)式GC"一樣.

  • 后臺(tái)GC線程再次調(diào)用SuspendEE函數(shù), 進(jìn)行標(biāo)記階段(Marking), 然后調(diào)用RestartEE函數(shù), 再進(jìn)行可以并行的清掃階段(Sweep).

  • 后臺(tái)GC已完成工作.

  • 服務(wù)器GC - 不啟用并發(fā)式GC

  • 用戶線程超過了分配預(yù)算并觸發(fā)了GC.

  • 服務(wù)器GC線程被喚醒, 并調(diào)用SuspendEE來停止所有受管理的線程(Managed Threads).

  • 服務(wù)器GC進(jìn)行回收工作(流程和工作站GC - 不啟用并發(fā)式GC相同).

  • 服務(wù)器GC線程調(diào)用RestartEE喚醒受管理的線程.

  • 用戶線程恢復(fù)運(yùn)行.

  • 服務(wù)器GC - 啟用并發(fā)式GC

    流程和工作站GC - 啟用并發(fā)式GC一樣, 除了非后臺(tái)的GC也會(huì)在服務(wù)器GC線程中完成.

    物理架構(gòu)

    這一段旨在幫助你追蹤代碼流程.

    當(dāng)用戶線程用完分配單位(Allocation Quantum), 需要一個(gè)新的分配單位時(shí)會(huì)調(diào)用try_allocate_more_space函數(shù).

    當(dāng)try_allocate_more_space函數(shù)需要觸發(fā)GC時(shí)會(huì)調(diào)用GarbageCollectGeneration函數(shù).

    在"工作站GC - 不啟用并發(fā)式GC"的模式下, GarbageCollectGeneration會(huì)在觸發(fā)GC的用戶線程上完成所有工作, 代碼流程是:

    GarbageCollectGeneration(){SuspendEE();garbage_collect();RestartEE();}garbage_collect(){generation_to_condemn();gc1();}gc1(){mark_phase();plan_phase();}plan_phase(){ ? ? // 實(shí)際的計(jì)劃階段, 判斷要用壓縮還是清掃if (compact){relocate_phase();compact_phase();} ? ? else ? ? ? ? make_free_lists();}

    在"工作站GC - 啟用并發(fā)式GC"的模式(默認(rèn)模式)下, 后臺(tái)GC的代碼流程是:

    GarbageCollectGeneration(){SuspendEE();garbage_collect();RestartEE();}garbage_collect(){generation_to_condemn(); ? ? // 判斷要用后臺(tái)GC, 喚醒后臺(tái)GCdo_background_gc();}do_background_gc(){init_background_gc();start_c_gc (); // 等待后臺(tái)GC完成工作并重啟受管理的線程wait_to_proceed();}bgc_thread_function() { ? ? while (1){ ? ? ? ? // 等待事件// 喚醒gc1();}}gc1(){background_mark_phase();background_sweep();}

    資源鏈接

    • .NET CLR GC Implementation

    • The Garbage Collection Handbook: The Art of Automatic Memory Management

    • Garbage collection (Wikipedia)

    譯者后注

    這份文檔簡(jiǎn)單的介紹了GC的設(shè)計(jì)和工作流程, 同時(shí)也帶來和很多疑問, 例如一個(gè)程序有多少個(gè)堆和什么是卡片等
    我將在CoreCLR源碼探索的系列文章中分析CoreCLR的GC源碼來解決這些疑問
    另外博客園上已經(jīng)有一位大神對(duì)CoreCLR的GC源碼進(jìn)行了部分分析,可以查看他的博客:http://www.cnblogs.com/kmsfan/p/5514473.html?

    相關(guān)文章:

    • 《代碼的未來》讀書筆記:內(nèi)存管理與GC那點(diǎn)事兒

    • CoreCLR源碼探索(一) Object是什么

    • CoreCLR源碼探索(二) new是什么

    • CoreCLR源碼探索(三) GC內(nèi)存分配器的內(nèi)部實(shí)現(xiàn)

    • .NET跨平臺(tái)之旅:corehost 是如何加載 coreclr 的

    • .NET CoreCLR開發(fā)人員指南(上)

    原文地址:http://www.cnblogs.com/zkweb/p/6288457.html


    .NET社區(qū)新聞,深度好文,微信中搜索dotNET跨平臺(tái)或掃描二維碼關(guān)注

    總結(jié)

    以上是生活随笔為你收集整理的CoreCLR文档翻译 - GC的设计的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。