自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

PerfView 洞察 C#托管堆內(nèi)存 "黑洞現(xiàn)象"

開發(fā) 前端
內(nèi)存黑洞 雖不算 CLR 的一個bug,但絕對是 CLR 可優(yōu)化的一個空間,分析這類問題是需要經(jīng)驗性的,分享出來供后來者少踩坑吧,畢竟在我的分析旅程中至少遇到了3次。

一:背景

1. 講故事

首先聲明的是這個 黑洞 是我定義的術語,它是用來表示 內(nèi)存吞噬 的一種現(xiàn)象,何為 內(nèi)存吞噬,我們來看一張圖。

圖片圖片

從上面的 卦象圖 來看,GCHeap 的 Allocated=852M 和 Committed=16.6G,它們的差值就是 分配緩沖區(qū)=16G,緩沖區(qū)的好處就是用空間換時間,弊端就是會實實在在的侵占內(nèi)存,擠壓其他程序的生存空間。

二:黑洞現(xiàn)象

1. 為什么會有黑洞現(xiàn)象

萬事皆有因果,今生的果是前世種的因,換句話說是程序曾經(jīng)有大量及頻繁的創(chuàng)建臨時對象,讓GC不自主的痙攣,小攣傷神,大攣傷身,所以GC為了避免大攣的發(fā)生,就大量的囤積本應該釋放掉的內(nèi)存,目的就是防止未來某個時刻再次有大內(nèi)存分配的發(fā)生。

2. 重現(xià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<string> list = new List<string>();

            for (int i = 0; i < 100000000; i++)
            {
                list.Add(i.ToString());
            }

            return "ok";
        }
    }

代碼非常簡單,每請求一次 /mytest 都會分配一個 1億 大小 List<string> 數(shù)組,而這個 List<string> 又是一個臨時對象,后續(xù)會被 GC 回收,接下來我們多請求幾次來調(diào)戲一下 GC,看他如何痙攣,截圖如下:

圖片圖片

從卦中看,我當前請求了 6 次,內(nèi)存峰值達到了 12G,因為是臨時對象,稍稍有一點回落,但此時已經(jīng)撐成一個大胖子了,接下來我們用 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.

從卦中看當前已經(jīng)有 6G 的緩沖區(qū)了,為了讓緩沖區(qū)更夸張,我們故意手工觸發(fā)一次 GC 即請求 /gc,觸發(fā)了GC之后,內(nèi)存從 10G 回落到了 7G 就不再降了,截圖如下:

圖片圖片

從卦中看,這兩個指標就更夸張了,GC 堆只有 1.1M 的對象,但預留了 7.1G 的內(nèi)存。

這個GC表現(xiàn)不管在 道德 還是 倫理 上都說不通的。

3. 找到前世的因

要想找到前世的因,手段有很多,比如用 WinDbg 觀察前世的托管堆,從殘留的 Committed - Allocated上就能找到因,也可以使用 PerfView 實時觀察,這里我們采用后者來洞察,使用默認的 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

采集一段時間后停止采集,接下來雙擊 GC Heap Net Mem (Coarse Sampling) Stacks 選項再選擇 WebApplication1 進程,通過 MaxMetric 指標看到曾經(jīng)峰值達到了 10.9G,截圖如下:

圖片圖片

毫無疑問的說,內(nèi)存峰值的時候必有妖怪,可以將峰值填入到 End 文本框中,然后雙擊內(nèi)存占比最高的 System.String[],觀察下它是誰分配的,截圖如下:

圖片圖片

從截圖中可以清晰的看到,原來是 Program.MyTest() 造的孽,至此真相大白。

4. 尋求化解之道

化解之道有很多:

  • 修改 GC 模式

簡而言之就是將 Server GC 改成 Workstation GC ,參考代碼如下:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <ServerGarbageCollection>false</ServerGarbageCollection>
  </PropertyGroup>

</Project>
  • 修改 Heap 個數(shù)

默認情況一個 cpucore 有一個 heap,我們可以盡量的減少 heap.count 的個數(shù),比如將 12 個改成 2 個。參考代碼如下:

{
   "runtimeOptions": {
      "configProperties": {
         "System.GC.HeapCount": 2
      }
   }
}
  • 大事化小

導致今世的果 是因為在內(nèi)存中短時的出現(xiàn)大對象,可以將大對象拆分成多批次的小對象處理,這樣可以達到后浪推前浪的的內(nèi)存復用,從源頭上繞過這個問題。

三:總結

內(nèi)存黑洞 雖不算 CLR 的一個bug,但絕對是 CLR 可優(yōu)化的一個空間,分析這類問題是需要經(jīng)驗性的,分享出來供后來者少踩坑吧,畢竟在我的分析旅程中至少遇到了3次。

責任編輯:武曉燕 來源: 一線碼農(nóng)聊技術
相關推薦

2023-07-17 11:25:35

.NET程序WinDbgPerfview

2023-11-01 08:07:42

.NETC#

2024-06-12 09:16:23

2022-08-26 00:00:01

C#內(nèi)存PerfView

2009-09-02 16:02:52

C#引用托管對象

2009-08-19 10:25:18

C#托管資源

2009-09-02 10:39:00

C#釋放托管資源

2009-08-28 16:43:08

AutoCAD托管C#

2011-05-18 17:56:38

C#C++

2011-05-18 18:05:47

C#C++

2009-08-25 09:49:09

C#內(nèi)存Graphic

2023-07-07 13:56:54

2009-08-17 13:49:20

C#中調(diào)用Window

2009-08-28 10:14:45

C#內(nèi)存泄露

2009-08-20 11:01:51

C#操作內(nèi)存

2009-09-03 16:58:49

C#內(nèi)存管理

2009-08-20 10:25:37

C#操作內(nèi)存

2011-05-20 15:37:05

MemoryStrea

2009-08-20 10:53:23

C#操作內(nèi)存

2010-01-25 15:55:50

托管C++
點贊
收藏

51CTO技術棧公眾號