聊一聊 C# 后臺GC 到底是怎么回事?

一:背景

寫這一篇的目的主要是因為.NET領域內幾本關于闡述GC方面的書,都是純理論,所以懂得人自然懂,不懂得人也沒法親自驗證,這一篇我就用 windbg + 源碼 讓大家眼見為實。

二:為什么要引入后臺GC

1. 后臺GC到底解決了什么問題

解決什么問題得先說有什么問題,我們知道 阻塞版GC 有一個顯著得特點就是,在 GC 觸發期間,所有的用戶線程都被 暫停了,這里的 暫停 是一個統稱,畫圖如下:

15076803221bf99654e9c90ab8e46418.png

這種 STW(Stop The World) 模式相信大家都習以為常了,但這里有一個很大的問題,不管當前 GC 是臨時代還是全量,還是壓縮或者標記,all in 全凍結,這種簡單粗暴的做法肯定是不可取的,也是 后臺GC 引入的先決條件。

那 后臺GC 到底解決了什么問題?

解決在 FullGC 模式下的 標記清除 回收期間,放飛用戶線程。

雖然這是一個很好的 Idea,但復雜度絕對上了幾個檔次。

三:后臺GC 詳解

1. 后臺 GC代碼 骨架圖

源碼面前,了無秘密,在coreclr 項目的 garbage-collection.md 文件中,描述了 后臺GC 的代碼流程圖。

GarbageCollectGeneration(){SuspendEE();garbage_collect();RestartEE();}garbage_collect(){generation_to_condemn();//?decide?to?do?a?background?GC//?wake?up?the?background?GC?thread?to?do?the?workdo_background_gc();}do_background_gc(){init_background_gc();start_c_gc?();//wait?until?restarted?by?the?BGC.wait_to_proceed();}bgc_thread_function(){while?(1){//?wait?on?an?event//?wake?upgc1();}}gc1(){background_mark_phase();background_sweep();}

可以清楚的看到就是在做 標記清除 且核心邏輯都在 background_mark_phase() 函數中,實現了標記的三個階段:?1.初始標記2.并發標記3.最終標記 , 其中 并發標記 階段,用戶線程是正常運行的,實現了將原來整個暫停 優化到了 2個小暫停。

2. 流程圖分析

為了方便說明,將三階段畫個圖如下:

92e77a74b31ccb16780be7ee8b3f2540.png

特別聲明:階段2的重啟是在 background_sweep() 方法中,而不是 最終標記(background_mark_phase) 階段。

  1. 初始標記

這個階段用戶線程處于暫停狀態,bgc 要做的事情就是從 線程棧終結器隊列 中尋找用戶根實現引用圖遍歷,然后再讓所有用戶線程啟動,簡化后的代碼如下:

void?gc_heap::background_mark_phase()
{dprintf(3,?("BGC:?stack?marking"));GCScan::GcScanRoots(background_promote_callback,max_generation,?max_generation,&sc);dprintf(3,?("BGC:?finalization?marking"));finalize_queue->GcScanRoots(background_promote_callback,?heap_number,?0);restart_vm();
}

接下來怎么驗證 階段1 是暫停狀態呢?為了方便講述,先上一段測試代碼:

internal?class?Program{static?List<string>?list?=?new?List<string>();static?void?Main(string[]?args){Debugger.Break();for?(int?i?=?0;?i?<?int.MaxValue;?i++){list.Add(String.Join(",",?Enumerable.Range(0,?100)));if?(i?%?10?==?0)?list.RemoveAt(0);}}}

然后用 windbg 在 background_mark_phase 函數下一個斷點:bp coreclr!WKS::gc_heap::background_mark_phase 即可。

0:009>?bp?coreclr!WKS::gc_heap::background_mark_phase
0:009>?g
Breakpoint?1?hit
coreclr!WKS::gc_heap::background_mark_phase:
00007ff9`e7bf73f4?488bc4??????????mov?????rax,rsp
0:008>?!t?-specialLock??DBG???ID?????OSID?ThreadOBJ???????????State?GC?Mode?????GC?Alloc?Context??????????????????Domain???????????Count?Apt?Exception0????1?????55d8?00000000006336B0????2a020?Preemptive??0000000000000000:0000000000000000?000000000062d650?-00001?MTA?(GC)?6????2?????568c?0000000000662F40????21220?Preemptive??0000000000000000:0000000000000000?000000000062d650?-00001?Ukn?(Finalizer)?8????4?????5730?0000000000676A90????21220?Preemptive??0000000000000000:0000000000000000?000000000062d650?-00001?Ukn?OSID?Special?thread?type0?55d8?SuspendEE?5?5688?DbgHelper?6?568c?Finalizer?8?5730?GC

可以清楚的看到,0號線程顯示了 SuspendEE 字樣,表示此時所有托管線程處于凍結狀態。

  1. 并發標記

這個階段就是各玩各的,用戶線程在正常執行,bgc在后臺進一步標記,因為是并行,所以存在 bgc 已標記好的對象引用關系被 用戶線程 破壞,所以 bgc 用 reset_write_watch 函數借助 windows 的內存頁監控,目的就是把那些臟頁找出來,在下一個階段來修正,簡化后的代碼如下:

void?gc_heap::background_mark_phase()
{disable_preemptive(true);//臟頁監控reset_write_watch(TRUE);revisit_written_pages(TRUE,?TRUE);dprintf(3,?("BGC:?handle?table?marking"));GCScan::GcScanHandles(background_promote,max_generation,?max_generation,&sc);disable_preemptive(false);
}

要想驗證此時的用戶線程是放飛的,可以在 revisit_written_pages 函數下一個斷點即可,使用命令:bp coreclr!WKS::gc_heap::revisit_written_pages

0:008>?!t?-specialLock??DBG???ID?????OSID?ThreadOBJ???????????State?GC?Mode?????GC?Alloc?Context??????????????????Domain???????????Count?Apt?Exception0????1?????55d8?00000000006336B0????2a020?Cooperative?000000000D1FD920:000000000D1FE120?000000000062d650?-00001?MTA?6????2?????568c?0000000000662F40????21220?Preemptive??0000000000000000:0000000000000000?000000000062d650?-00001?Ukn?(Finalizer)?8????4?????5730?0000000000676A90????21220?Cooperative?0000000000000000:0000000000000000?000000000062d650?-00001?Ukn?OSID?Special?thread?type5?5688?DbgHelper?6?568c?Finalizer?8?5730?GC

看到沒有,那個 SuspendEE 神奇的消失了,而且 0 號線程的 GC 模式也改成了 Cooperative,表示可允許操控 托管堆。

  1. 最終標記

等 bgc 在后臺做的差不多了,就可以再來一次 SupendEE,將 并發標記 期間由用戶線程造成的臟引用進行最終一次修正,修正的數據來源就是監控到的 Windows臟頁,代碼就不上了,我們聊下怎么去驗證階段二又回到了 SuspendEE 狀態?可以在 background_sweep() 函數下一個斷點, 命令: bp coreclr!WKS::gc_heap::background_sweep

0:000>?bp?coreclr!WKS::gc_heap::background_sweep
0:000>?g
coreclr!WKS::gc_heap::background_sweep:
00007ff9`e7b7a2e0?4053????????????push????rbx
0:008>?!t?-specialLock??DBG???ID?????OSID?ThreadOBJ???????????State?GC?Mode?????GC?Alloc?Context??????????????????Domain???????????Count?Apt?Exception0????1?????55d8?00000000006336B0????2a020?Preemptive??0000000000000000:0000000000000000?000000000062d650?-00001?MTA?6????2?????568c?0000000000662F40????21220?Preemptive??0000000000000000:0000000000000000?000000000062d650?-00001?Ukn?(Finalizer)?8????4?????5730?0000000000676A90????21220?Preemptive??0000000000000000:0000000000000000?000000000062d650?-00001?Ukn?(GC)?OSID?Special?thread?type5?5688?DbgHelper?6?568c?Finalizer?8?5730?GC?SuspendEE

哈哈,可以看到那個 SuspendEE 又回來了。

3. 后臺GC 只會在 fullGC 模式下嗎?

這是最后一個要讓大家眼見為實的問題,在gc觸發期間,內部會維護一個 gc_mechanisms 結構體,其中就記錄了當前 GC 觸發的種種信息,可以用 windbg 把它導出來看看便知。

0:008>?x?coreclr!*settings*
00007ff9`e7f82e90?coreclr!WKS::gc_heap::settings?=?class?WKS::gc_mechanisms
0:008>?dt?coreclr!WKS::gc_heap::settings?00007ff9`e7f82e90+0x000?gc_index?????????:?0xb3+0x008?condemned_generation?:?0n2+0x00c?promotion????????:?0n1+0x010?compaction???????:?0n0+0x014?loh_compaction???:?0n0+0x018?heap_expansion???:?0n0+0x01c?concurrent???????:?1+0x020?demotion?????????:?0n0+0x024?card_bundles?????:?0n1+0x028?gen0_reduction_count?:?0n0+0x02c?should_lock_elevation?:?0n0+0x030?elevation_locked_count?:?0n0+0x034?elevation_reduced?:?0n0+0x038?minimal_gc???????:?0n0+0x03c?reason???????????:?0?(?reason_alloc_soh?)+0x040?pause_mode???????:?1?(?pause_interactive?)+0x044?found_finalizers?:?0n1+0x048?background_p?????:?0n0+0x04c?b_state??????????:?0?(?bgc_not_in_process?)+0x050?allocations_allowed?:?0n1+0x054?stress_induced???:?0n0+0x058?entry_memory_load?:?0x49+0x060?entry_available_physical_mem?:?0x00000001`0a50d000+0x068?exit_memory_load?:?0

condemned_generation=2 可知當前觸發的是 2 代GC,原因是代滿了 reason : 0 ( reason_alloc_soh )

四:總結

看的再多還不如實操一遍,如果覺得手工編譯 coreclr 源碼麻煩,可以考慮下 windbg,好了,本篇就聊這么多,希望對你有幫助。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/284676.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/284676.shtml
英文地址,請注明出處:http://en.pswp.cn/news/284676.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【BIM入門實戰】Revit中的墻體層次以及常見問題解答

一、Revit墻體的層次 1. Revit墻體的層次如圖 Revit繪制墻體時,要先選擇定位線,可以選核心層中心線,也可以選墻中心線,當墻體為對稱時,核心層中心線與墻中心線會重合。 2. 具體層次 1)結構[1]:必須在核心邊界內 2)襯底[2]:其他材質基礎的材料,如膠合板或石膏板 3…

Spring Boot 使用Redis

轉載自&#xff1a;http://www.cnblogs.com/ityouknow/p/5748830.html Redis支持更豐富的數據結構&#xff0c;例如hashes, lists, sets等&#xff0c;同時支持數據持久化。除此之外&#xff0c;Redis還提供一些類數據庫的特性&#xff0c;比如事務&#xff0c;HA&#xff0c;主…

工具鏈接

OmniGraffle Pro 7.0.2 Mac中文破解版 | 史蒂芬周的博客        http://www.sdifenzhou.com/omnigrafflepro702.html 轉載于:https://www.cnblogs.com/wfwenchao/p/6393097.html

FlashCache初體驗

FlashCache初體驗 注意&#xff1a; 測試用的是CentOS6.5 內核版本2.6.32-431.el6.x86_64 步驟&#xff1a; 上傳CentOS6.5做本地yum源&#xff0c;安裝以下包。 yum install gcc yum install *kernel* yum install perl 將flashcache master打包下載至測試機上&#xff0c;可以…

用python將指定目錄下的所有json文件合并成一個csv文件

#!/usr/bin/env python # -*- encoding: utf-8 -*-import sys import json import os import pandas as pd import csv""" 獲取文件名列表 """ def list_file_names(folder):exist_files os.listdir(folder)file_list []for f in exist_files:…

【系統設計】分布式鍵值數據庫

鍵值存儲 ( key-value store )&#xff0c;也稱為 K/V 存儲或鍵值數據庫&#xff0c;這是一種非關系型數據庫。每個值都有一個唯一的 key 關聯&#xff0c;也就是我們常說的 鍵值對。常見的鍵值存儲有 Redis, Amazon DynamoDB&#xff0c;Microsoft Azure Cosmos DB&#xff0c…

keras系列︱Application中五款已訓練模型、VGG16框架(Sequential式、Model式)解讀(二)...

引自&#xff1a;http://blog.csdn.net/sinat_26917383/article/details/72859145 中文文檔&#xff1a;http://keras-cn.readthedocs.io/en/latest/ 官方文檔&#xff1a;https://keras.io/ 文檔主要是以keras2.0。 . . Keras系列&#xff1a; 1、keras系列︱Sequential與Mo…

【BIM入門實戰】Revit建筑墻體:構造、包絡、疊層圖文詳解

本文主要講解Revit建筑墻體:構造、包絡、疊層。 一、基本墻 第一步: 選擇菜單欄的【建筑】選項卡中的【墻】下拉菜單→【屬性】面板中切換至基本墻→點擊屬性面板中的【編輯類型】,彈出如下墻體對話框。 第二步: 選擇【復制】按鈕→重新進行編輯名稱,命名為“外墻-1F-2…

win11 恢復win10開始菜單及任務欄

Windows Registry Editor Version 5.00[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced] "Start_ShowClassicMode"dword:00000001 "TaskbarSi"dword:00000000將上述代碼存為reg文件&#xff0c;雙擊導入注冊表。 任務欄…

CentOS安裝Tomcat

1. 下載Tomcat安裝包&#xff1a; Tomcat官網 解壓下載下來的tar.gz至任意目錄下&#xff0c;執行命令&#xff1a; Java代碼 tar -xzf apache-tomcat-7.0.56.tar.gz 解壓后如圖&#xff1a; 如果是在windows上&#xff0c;則直接解壓zip包到任意目錄&…

【BIM+GIS】ArcGIS Pro2.8如何打開Revit模型,BIM和GIS融合?

ArcGIS Pro2.8中,可以直接打開Revit模型(.rvt)項目文件,實現了從數據格式方面BIM與GIS的有機融合,具體操作如下所示: 1. Revit2018模型繪制 打開Revit2018軟件,選擇【建筑樣板】,打開標高1樓層平面,新建一個簡單的戶型:包括四面墻體、2個門和一扇窗戶,如下圖所示。…

Edge 開發者沙龍|一小時精通Edge擴展開發

點擊藍字關注我們編輯&#xff1a;Alan Wang排版&#xff1a;Rani Sun微軟 Reactor 為幫助廣開發者&#xff0c;技術愛好者&#xff0c;更好的學習 .NET Core, C#, Python&#xff0c;數據科學&#xff0c;機器學習&#xff0c;AI&#xff0c;區塊鏈, IoT 等技術&#xff0c;將…

tomcat 開啟遠程debug

1、linux服務器上tomcat配置startup.sh 文件末尾添加&#xff08;不換行&#xff09;&#xff1a;declare -x CATALINA_OPTS"-server -Xdebug -Xnoagent -Djava.compilerNONE -Xrunjdwp:transportdt_socket,servery,suspendn,address9876"2、eclipse配置&#xff1a;…

公歷還是很簡單的

1 import java.util.*;2 class CalendarTest3 {4 /*先輸出提示語句&#xff0c;并接受用戶輸入的年、月。5 根據用戶輸入的年&#xff0c;先判斷是否是閏年。6 根據用戶輸入的年份來判斷月的天數。7 用循環計算用戶輸入的年份距1900年1月1日的總天數。8 用…

【測繪程序設計】坐標反算神器V1.0(附C/C#/VB源程序)

【拓展閱讀】:【測繪程序設計】坐標正算神器V1.0(附C/C#/VB源程序) 一、坐標反算原理 ?坐標反算:已知兩點坐標,反求邊長和方位角,稱為坐標反算。 原理坐標系: 計算公式: 二、C#程序實現 1. 界面設計 2

在二維數組中查找一個數

在一個二維數組中&#xff0c;每一行都按照從左到右遞增的順序排列&#xff0c;每一列也按照從上到下遞增的順序排列。在這樣一個序列中查找一個數1 2 8 92 4 9 124 7 10 136 8 11 15例如查找7&#xff0c;就從第一行的最左邊查找&#xff0c;9大于7&#xff0c;則9以下的也不用…

ASP.NET Core 6框架揭秘實例演示[01]: 編程初體驗

本篇提供的20個簡單的演示實例基本涵蓋了ASP.NET Core 6基本的編程模式&#xff0c;我們不僅會利用它們來演示針對控制臺、API、MVC、gRPC應用的構建與編程&#xff0c;還會演示Dapr在.NET 6中的應用。除此之外&#xff0c;這20個實例還涵蓋了針對依賴注入、配置選項、日志記錄…

DBeaverEE 21.1.0安裝指南

1、 安裝jdk11 2、 配置環境變量 將jdk11安裝目錄加入path&#xff1a;C:\Program Files\Java\jdk-11.0.10\bin3、 安裝DBEE 21.1 4、 將dbeaver-agent文件夾復制到DBEE安裝目錄 5、將DBEE安裝目錄下的jre目錄刪除或改名 6、 修改dbeaver.ini文件&#xff0c;在文件最后添加…

跟風學Docker之四:Docker網絡解決方案

2019獨角獸企業重金招聘Python工程師標準>>> 跟風學Docker之四&#xff1a;Docker網絡解決方案 博客分類&#xff1a; docker 前言&#xff1a;前面的部分一直都是單機跑docker&#xff0c;但實際生產環境不可能只用一臺來跑。肯定會用到多臺&#xff0c;因為他們都…

C++中數字和字符的轉換

參考&#xff1a;http://blog.csdn.net/xw20084898/article/details/21939811 http://nnssll.blog.51cto.com/902724/198237/ http://www.cnblogs.com/luxiaoxun/archive/2012/08/03/2621803.html 一、stringstream通常是用來做數據轉換的。 1、例如int轉string:#include <s…