咱們有一家top級的淘品牌店鋪,爲了後續的加速計算,在程序啓動的時候灌入她家的核心數據到內存中,灌入完成後內存高達100G,雖然雲上的機器內存有256G,然被這麼劃掉一半看着仍是有一點心疼的,可憐那些被擠壓的小囉囉程序😄😄😄,本覺得是那些List,HashSet,Dictionary須要動態擴容虛佔了不少內存,也就沒當一回事,後來過了一天發現內存回到了大概70多G,臥槽,不是所謂的集合虛佔,而是GC沒給我回收呀。。。程序員
爲了驗證個人說法,我就不去生產抓這個龐然大物的dump了,去測試環境給你們抓一個,晚上清蒸。算法
!eeheap -gc 查看gc信息express
0:000> !eeheap -gc Number of GC Heaps: 1 generation 0 starts at 0x0000019b0fc66b48 generation 1 starts at 0x0000019b0f73b138 generation 2 starts at 0x0000019a5da81000 ephemeral segment allocation context: none segment begin allocated size 0000019a5da80000 0000019a5da81000 0000019a6da7ffb8 0xfffefb8(268431288) 0000019a00000000 0000019a00001000 0000019a0ffffe90 0xfffee90(268430992) 0000019a10000000 0000019a10001000 0000019a1ffffeb0 0xfffeeb0(268431024) 0000019a20000000 0000019a20001000 0000019a2fffffb0 0xfffefb0(268431280) 0000019a30000000 0000019a30001000 0000019a3ffffc50 0xfffec50(268430416) 0000019a40000000 0000019a40001000 0000019a4fffffc8 0xfffefc8(268431304) 0000019a7aad0000 0000019a7aad1000 0000019a8aacfd60 0xfffed60(268430688) 0000019a8cbf0000 0000019a8cbf1000 0000019a9cbefe10 0xfffee10(268430864) 0000019a9cbf0000 0000019a9cbf1000 0000019aacbefcb8 0xfffecb8(268430520) 0000019aacbf0000 0000019aacbf1000 0000019abcbefd18 0xfffed18(268430616) 0000019abcbf0000 0000019abcbf1000 0000019accbefd68 0xfffed68(268430696) 0000019accbf0000 0000019accbf1000 0000019adcbefcf8 0xfffecf8(268430584) 0000019adcbf0000 0000019adcbf1000 0000019aecbefdc0 0xfffedc0(268430784) 0000019af0e20000 0000019af0e21000 0000019b00e1ff28 0xfffef28(268431144) 0000019b00e20000 0000019b00e21000 0000019b10047178 0xf226178(253911416) Large object heap starts at 0x0000019a6da81000 segment begin allocated size 0000019a6da80000 0000019a6da81000 0000019a756d0480 0x7c4f480(130348160) 0000019b10e20000 0000019b10e21000 0000019b133ca330 0x25a9330(39490352) Total Size: Size: 0xf940ee70 (4181782128) bytes. ------------------------------ GC Heap Size: Size: 0xf940ee70 (4181782128) bytes.
從最後一行能夠看到堆大小: GC Heap Size: Size: 0xf940ee70 (4181782128) bytes.
而後將4181782128 byte 轉化爲GB: 4181782128/1024/1024/1024= 3.89G
。服務器
而後再來看一下3代中有多少須要free的對象,佔了多少空間,爲了方便查看,你們能夠用一下sosex擴展,提供了不少方便的方法。測試
!dumpgen xxxx 依次把0,1,2 三個代中的free空間統計出來。ui
0:000> !dumpgen 0 -free -stat Count Total Size Type ------------------------------------------------- 168 1,120,008 **** FREE **** 168 objects, 1,120,008 bytes 0:000> !dumpgen 1 -free -stat Count Total Size Type ------------------------------------------------- 368 8,096 **** FREE **** 368 objects, 8,096 bytes 0:000> !dumpgen 2 -free -stat Count Total Size Type ------------------------------------------------- 11,857,034 1,052,310,524 **** FREE **** 11,857,034 objects, 1,052,310,524 bytes
從上面輸出能夠看到,三個代中須要free的信息:lua
對象有:168 + 368 + 11857034 = 11857570個
,操作系統
空間:1120008 + 8096 + 1052310524 = 1053438628 byte => 0.98G
。線程
驚訝吧~, 3.89G的堆,等待被釋放的空間有0.98G,佔比高達25%,再看看第2代中有高達1185萬的對象須要清理,說明在整個加載過程當中,GC至少被觸發2次。。。3d
因此等GC本身啓動回收不知道猴年馬月,爲了高效利用內存,不得已本身先給程序點個火,讓程序內存降到了 3.89 - 0.98 = 2.91 G
。
有很多程序員對gc中的代管理機制不是特別清楚,或者看過書以後理解也停留在理論上,無法去驗證書中所說,其實我也不是特別理解,😄😄😄,做爲一個準備好好玩自媒體人,不能讓您白來一趟哈。
當CLR不當心錯入程序世界的時候,會給你分配兩個堆,一個叫作小對象堆,一個叫作大對象堆,默認是以83k做爲大小堆的分界線,固然你也能夠自定義配置,堆上的空間由不少的內存段拼成的,可能你有點蒙,我畫張圖吧。
看完上圖,可能你們有兩個疑問:
這是由於CLR作了不少假設,它假設在gen0和gen1上回收的對象會特別多,因此沒事就上去轉轉,CLR爲了方便GC快速清理回收壓縮。。。就將gen0和gen1都放置在這個臨時內存段上。
你可能要問,有證據嗎??? 我就拿剛纔的4G程序說話吧。
0:000> !eeheap -gc Number of GC Heaps: 1 generation 0 starts at 0x0000019b0fc66b48 generation 1 starts at 0x0000019b0f73b138 generation 2 starts at 0x0000019a5da81000 ephemeral segment allocation context: none segment begin allocated size 0000019a5da80000 0000019a5da81000 0000019a6da7ffb8 0xfffefb8(268431288) 0000019a00000000 0000019a00001000 0000019a0ffffe90 0xfffee90(268430992) 0000019a10000000 0000019a10001000 0000019a1ffffeb0 0xfffeeb0(268431024) 0000019a20000000 0000019a20001000 0000019a2fffffb0 0xfffefb0(268431280) 0000019a30000000 0000019a30001000 0000019a3ffffc50 0xfffec50(268430416) 0000019a40000000 0000019a40001000 0000019a4fffffc8 0xfffefc8(268431304) 0000019a7aad0000 0000019a7aad1000 0000019a8aacfd60 0xfffed60(268430688) 0000019a8cbf0000 0000019a8cbf1000 0000019a9cbefe10 0xfffee10(268430864) 0000019a9cbf0000 0000019a9cbf1000 0000019aacbefcb8 0xfffecb8(268430520) 0000019aacbf0000 0000019aacbf1000 0000019abcbefd18 0xfffed18(268430616) 0000019abcbf0000 0000019abcbf1000 0000019accbefd68 0xfffed68(268430696) 0000019accbf0000 0000019accbf1000 0000019adcbefcf8 0xfffecf8(268430584) 0000019adcbf0000 0000019adcbf1000 0000019aecbefdc0 0xfffedc0(268430784) 0000019af0e20000 0000019af0e21000 0000019b00e1ff28 0xfffef28(268431144) 0000019b00e20000 0000019b00e21000 0000019b10047178 0xf226178(253911416) Large object heap starts at 0x0000019a6da81000 segment begin allocated size 0000019a6da80000 0000019a6da81000 0000019a756d0480 0x7c4f480(130348160) 0000019b10e20000 0000019b10e21000 0000019b133ca330 0x25a9330(39490352) Total Size: Size: 0xf940ee70 (4181782128) bytes. ------------------------------ GC Heap Size: Size: 0xf940ee70 (4181782128) bytes.
從上面gc信息中能夠看到小對象堆中目前有 15個內存段, 大對象堆有2個內存段, gen0的起始地址爲0x0000019b0fc66b48
,gen1的起始地址爲0x0000019b0f73b138
, 都落在了第15個內存段內 0000019b00e20000 0000019b00e21000 0000019b10047178 0xf226178(253911416)
,其他內存段都被 gen2 佔領,若是你們有點亂,先多看幾遍,等一下看個人演示。
這個段的大小,須要看是x64仍是x86機器,還要看GC是工做站模式仍是服務器模式,不過msdn幫咱們總結了,https://docs.microsoft.com/zh-cn/dotnet/standard/garbage-collection/fundamentals , 截個圖給你們看一下。
個人本機是x64版本,工做站模式,能夠經過 !eeversion
查看一下。
0:000> !eeversion 4.8.3801.0 free Workstation mode SOS Version: 4.8.3801.0 retail build
對應圖中,個人臨時內存段的最大內存是256M,再回過頭用4G程序的來驗證一下內存段大小,用 allocated - begin
便可。
ephemeral segment allocation context: none segment begin allocated size 0000019b00e20000 0000019b00e21000 0000019b10047178 0xf226178(253911416) 0:000> ? 0000019b10047178 - 0000019b00e21000 Evaluate expression: 253911416 = 00000000`0f226178
二者差值爲 253911416 byte => 242M
,能夠看出離256M不遠了,等到了256M又要觸發GC啦。。。。
有了上面的基礎,我以爲你對GC的gen機制應該明白了,因爲3個gen運行時預約空間是隨GC觸發隨時變更,因此就不知道某個時刻各個gen當時的空間觸發閾值。
接下來講一下三代的原理:當gen0滿了會觸發GC回收,將gen0中活對象送到gen1中,死的就消滅掉,當某時候gen1滿了,gen1的活對象會被送到gen2中,當下個某一次gen2滿了,就向操做系統申請新的內存段,因此你看到了4G程序佔用了多達14個內存段,就是這麼一個道理,沒什麼複雜的。
我剛纔也說了,不少人知道這個理論,不知道怎麼去驗證,這裏我就演示一下,先上代碼:
public static void Main(string[] args) { Student student1 = new Student() { UserName = "cnblogs", Email = "cnblogs@qq.com" }; Student student2 = new Student() { UserName = "csdn", Email = "csdn@qq.com" }; Console.WriteLine("兩個對象已建立!雙雙進入 Gen0"); Console.Read(); student1 = null; GC.Collect(); Console.WriteLine("Student1 已從Gen0中抹掉,助力Student2上Gen1,是否繼續?"); Console.ReadKey(); GC.Collect(); Console.WriteLine("再次助力Student2上Gen2"); Console.ReadKey(); Console.WriteLine("所有執行結束!"); Console.ReadLine(); } } public class Student { public string UserName { get; set; } public string Email { get; set; } }
代碼很簡單,就是想讓你看一下student1和student2如何在gen0,gen1,gen2中游蕩,而且給你精準找出來。
先啓動程序,抓一下dump文件。
0:000> !clrstack -l ConsoleApp4.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp4\Program.cs @ 18] LOCALS: 0x000000017d7feeb8 = 0x000001d0962c2f28 0x000000017d7feeb0 = 0x000001d0962c2f48 0:000> !eeheap -gc Number of GC Heaps: 1 generation 0 starts at 0x000001d0962c1030 generation 1 starts at 0x000001d0962c1018 generation 2 starts at 0x000001d0962c1000 ephemeral segment allocation context: none segment begin allocated size 000001d0962c0000 000001d0962c1000 000001d0962c7fe8 0x6fe8(28648) Large object heap starts at 0x000001d0a62c1000 segment begin allocated size 000001d0a62c0000 000001d0a62c1000 000001d0a62c9a68 0x8a68(35432) Total Size: Size: 0xfa50 (64080) bytes. ------------------------------ GC Heap Size: Size: 0xfa50 (64080) bytes.
仔細看上面的輸出,從主線程的堆棧上能夠看到student1和studnet2的地址依次爲0x000001d0962c2f28, 0x000001d0962c2f48
,而gen0的起始地址爲:0x000001d0962c1030
,恰好落在 gen0 的區間內,可能你有點蒙,我畫一張圖。
按下Enter鍵,執行後續代碼將student1=null,再執行GC操做,看下堆中又是如何?
0:000> !clrstack -l ConsoleApp4.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp4\Program.cs @ 24] LOCALS: 0x000000607e9fea50 = 0x0000000000000000 0x000000607e9fea48 = 0x0000017f0dff2f38 000000607e9fec88 00007ff8e9396c93 [GCFrame: 000000607e9fec88] 0:000> !eeheap -gc Number of GC Heaps: 1 generation 0 starts at 0x0000017f0dff6ea0 generation 1 starts at 0x0000017f0dff1018 generation 2 starts at 0x0000017f0dff1000 ephemeral segment allocation context: none segment begin allocated size 0000017f0dff0000 0000017f0dff1000 0000017f0dff8eb8 0x7eb8(32440) Large object heap starts at 0x0000017f1dff1000 segment begin allocated size 0000017f1dff0000 0000017f1dff1000 0000017f1dff9a68 0x8a68(35432) Total Size: Size: 0x10920 (67872) bytes. ------------------------------ GC Heap Size: Size: 0x10920 (67872) bytes.
若是弄明白了上一個案例,看這裏就很簡單了,很清楚的看到studnet2落在了gen1區間段,不過從起始地址上看,gen1的空間變大了。。。我繼續畫一張圖。
0:000> !clrstack -l ConsoleApp4.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp4\Program.cs @ 28] LOCALS: 0x000000d340bfebb0 = 0x0000000000000000 0x000000d340bfeba8 = 0x00000217b5df2f38 000000d340bfede8 00007ff8e9396c93 [GCFrame: 000000d340bfede8] 0:000> !eeheap -gc Number of GC Heaps: 1 generation 0 starts at 0x00000217b5df6f40 generation 1 starts at 0x00000217b5df6ea0 generation 2 starts at 0x00000217b5df1000 ephemeral segment allocation context: none segment begin allocated size 00000217b5df0000 00000217b5df1000 00000217b5df8f58 0x7f58(32600) Large object heap starts at 0x00000217c5df1000 segment begin allocated size 00000217c5df0000 00000217c5df1000 00000217c5df9a68 0x8a68(35432) Total Size: Size: 0x109c0 (68032) bytes. ------------------------------ GC Heap Size: Size: 0x109c0 (68032) bytes.
很簡單,我就不畫圖了哈,student2的內存地址但是落在 gen2上哦~😄😄😄
GC.Collect儘可能少用,省的把內部的分配和回收算法搞亂了,非要用的話也要理解以後再根據本身的場景使用哈。
本篇就說到這裏,但願對你有幫助