新聞中心
一:背景
1. 講故事
首先聲明的是這個(gè) 黑洞 是我定義的術(shù)語,它是用來表示 內(nèi)存吞噬 的一種現(xiàn)象,何為 內(nèi)存吞噬,我們來看一張圖。

創(chuàng)新互聯(lián)建站服務(wù)項(xiàng)目包括全州網(wǎng)站建設(shè)、全州網(wǎng)站制作、全州網(wǎng)頁制作以及全州網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,全州網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到全州省份的部分城市,未來相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
圖片
從上面的 卦象圖 來看,GCHeap 的 Allocated=852M 和 Committed=16.6G,它們的差值就是 分配緩沖區(qū)=16G,緩沖區(qū)的好處就是用空間換時(shí)間,弊端就是會(huì)實(shí)實(shí)在在的侵占內(nèi)存,擠壓其他程序的生存空間。
二:黑洞現(xiàn)象
1. 為什么會(huì)有黑洞現(xiàn)象
萬事皆有因果,今生的果是前世種的因,換句話說是程序曾經(jīng)有大量及頻繁的創(chuàng)建臨時(shí)對(duì)象,讓GC不自主的痙攣,小攣傷神,大攣傷身,所以GC為了避免大攣的發(fā)生,就大量的囤積本應(yīng)該釋放掉的內(nèi)存,目的就是防止未來某個(gè)時(shí)刻再次有大內(nèi)存分配的發(fā)生。
2. 重現(xiàn)今生的果
我相信因果關(guān)系大家都弄清楚了,但口說無憑,還得用代碼證明一下不是?為了模擬GC痙攣,上一段測試代碼。
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthorization();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseAuthorization();
app.MapGet("/mytest", (HttpContext httpContext) =>
{
return MyTest();
});
app.MapGet("/gc", (HttpContext httpContext) =>
{
GC.Collect();
return 1;
});
app.Run();
}
public static string MyTest()
{
List list = new List();
for (int i = 0; i < 100000000; i++)
{
list.Add(i.ToString());
}
return "ok";
}
} 代碼非常簡單,每請(qǐng)求一次 /mytest 都會(huì)分配一個(gè) 1億 大小 List
圖片
從卦中看,我當(dāng)前請(qǐng)求了 6 次,內(nèi)存峰值達(dá)到了 12G,因?yàn)槭桥R時(shí)對(duì)象,稍稍有一點(diǎn)回落,但此時(shí)已經(jīng)撐成一個(gè)大胖子了,接下來我們用 WinDbg 附加一下,觀察下 Allocated 和 Committed 閾值。
0:033> !eeheap -gc
========================================
Number of GC Heaps: 12
----------------------------------------
...
Heap 11 (0000023513f26c10)
generation 0 starts at 23351c3aab8
generation 1 starts at 233484c38e0
generation 2 starts at 233484c1000
ephemeral segment allocation context: none
Small object heap
segment begin allocated committed allocated size committed size
0233484c0000 0233484c1000 02335c794ad0 023379ad2000 0x142d3ad0 (338508496) 0x31612000 (828448768)
Large object heap starts at 234384c1000
segment begin allocated committed allocated size committed size
0234384c0000 0234384c1000 0234384c1018 0234384e2000 0x18 (24) 0x22000 (139264)
Pinned object heap starts at 234f84c1000
segment begin allocated committed allocated size committed size
0234f84c0000 0234f84c1000 0234f84c1018 0234f84c2000 0x18 (24) 0x2000 (8192)
------------------------------
GC Allocated Heap Size: Size: 0x14f241378 (5622731640) bytes.
GC Committed Heap Size: Size: 0x2b125c000 (11561975808) bytes.從卦中看當(dāng)前已經(jīng)有 6G 的緩沖區(qū)了,為了讓緩沖區(qū)更夸張,我們故意手工觸發(fā)一次 GC 即請(qǐng)求 /gc,觸發(fā)了GC之后,內(nèi)存從 10G 回落到了 7G 就不再降了,截圖如下:
圖片
從卦中看,這兩個(gè)指標(biāo)就更夸張了,GC 堆只有 1.1M 的對(duì)象,但預(yù)留了 7.1G 的內(nèi)存。
這個(gè)GC表現(xiàn)不管在 道德 還是 倫理 上都說不通的。
3. 找到前世的因
要想找到前世的因,手段有很多,比如用 WinDbg 觀察前世的托管堆,從殘留的 Committed - Allocated上就能找到因,也可以使用 PerfView 實(shí)時(shí)觀察,這里我們采用后者來洞察,使用默認(rèn)的 Command 參數(shù)。
PerfView.exe "/DataFile:PerfViewData.etl" /BufferSizeMB:256 /StackCompression /CircularMB:500 /ClrEvents:GC,Binder,Security,AppDomainResourceManagement,Contention,Exception,Threading,JITSymbols,Type,GCHeapSurvivalAndMovement,GCHeapAndTypeNames,Stack,ThreadTransfer,Codesymbols,Compilation /NoGui /NoNGenRundown /Merge:True /Zip:True collect采集一段時(shí)間后停止采集,接下來雙擊 GC Heap Net Mem (Coarse Sampling) Stacks 選項(xiàng)再選擇 WebApplication1 進(jìn)程,通過 MaxMetric 指標(biāo)看到曾經(jīng)峰值達(dá)到了 10.9G,截圖如下:
圖片
毫無疑問的說,內(nèi)存峰值的時(shí)候必有妖怪,可以將峰值填入到 End 文本框中,然后雙擊內(nèi)存占比最高的 System.String[],觀察下它是誰分配的,截圖如下:
圖片
從截圖中可以清晰的看到,原來是 Program.MyTest() 造的孽,至此真相大白。
4. 尋求化解之道
化解之道有很多:
- 修改 GC 模式
簡而言之就是將 Server GC 改成 Workstation GC ,參考代碼如下:
false
- 修改 Heap 個(gè)數(shù)
默認(rèn)情況一個(gè) cpucore 有一個(gè) heap,我們可以盡量的減少 heap.count 的個(gè)數(shù),比如將 12 個(gè)改成 2 個(gè)。參考代碼如下:
{
"runtimeOptions": {
"configProperties": {
"System.GC.HeapCount": 2
}
}
}- 大事化小
導(dǎo)致今世的果 是因?yàn)樵趦?nèi)存中短時(shí)的出現(xiàn)大對(duì)象,可以將大對(duì)象拆分成多批次的小對(duì)象處理,這樣可以達(dá)到后浪推前浪的的內(nèi)存復(fù)用,從源頭上繞過這個(gè)問題。
三:總結(jié)
內(nèi)存黑洞 雖不算 CLR 的一個(gè)bug,但絕對(duì)是 CLR 可優(yōu)化的一個(gè)空間,分析這類問題是需要經(jīng)驗(yàn)性的,分享出來供后來者少踩坑吧,畢竟在我的分析旅程中至少遇到了3次。
標(biāo)題名稱:PerfView 洞察 C#托管堆內(nèi)存 "黑洞現(xiàn)象"
URL鏈接:http://fisionsoft.com.cn/article/cdeicjg.html


咨詢
建站咨詢
