内存迟迟下不去,可能你就差一个GC.Collect
一:背景
1. 講故事
我們有一家top級(jí)的淘品牌店鋪,為了后續(xù)的加速計(jì)算,在程序啟動(dòng)的時(shí)候灌入她家的核心數(shù)據(jù)到內(nèi)存中,灌入完成后內(nèi)存高達(dá)100G,雖然云上的機(jī)器內(nèi)存有256G,然被這么劃掉一半看著還是有一點(diǎn)心疼的,可憐那些被擠壓的小啰啰程序????????????,本以為是那些List,HashSet,Dictionary需要?jiǎng)討B(tài)擴(kuò)容虛占了很多內(nèi)存,也就沒當(dāng)一回事,后來過了一天發(fā)現(xiàn)內(nèi)存回到了大概70多G,臥槽,不是所謂的集合虛占,而是GC沒給我回收呀。。。
2. windbg驗(yàn)證一下
為了驗(yàn)證我的說法,我就不去生產(chǎn)抓這個(gè)龐然大物的dump了,去測(cè)試環(huán)境給大家抓一個(gè),晚上清蒸。
!eeheap -gc 查看gc信息
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: nonesegment 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 0x0000019a6da81000segment 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 轉(zhuǎn)化為GB:?4181782128/1024/1024/1024= 3.89G。
然后再來看一下3代中有多少需要free的對(duì)象,占了多少空間,為了方便查看,大家可以用一下sosex擴(kuò)展,提供了很多方便的方法。
!dumpgen xxxx 依次把0,1,2 三個(gè)代中的free空間統(tǒng)計(jì)出來。
0:000> !dumpgen 0 -free -statCount Total Size Type -------------------------------------------------168 1,120,008 **** FREE ****168 objects, 1,120,008 bytes0:000> !dumpgen 1 -free -statCount Total Size Type -------------------------------------------------368 8,096 **** FREE ****368 objects, 8,096 bytes0:000> !dumpgen 2 -free -statCount Total Size Type -------------------------------------------------11,857,034 1,052,310,524 **** FREE ****11,857,034 objects, 1,052,310,524 bytes從上面輸出可以看到,三個(gè)代中需要free的信息:
對(duì)象有:168 + 368 + 11857034 = 11857570個(gè),
空間:1120008 + 8096 + 1052310524 = 1053438628 byte => 0.98G。
驚訝吧~, 3.89G的堆,等待被釋放的空間有0.98G,占比高達(dá)25%,再看看第2代中有高達(dá)1185萬的對(duì)象需要清理,說明在整個(gè)加載過程中,GC至少被觸發(fā)2次。。。
所以等GC自己?jiǎn)?dòng)回收不知道猴年馬月,為了高效利用內(nèi)存,不得已自己先給程序點(diǎn)個(gè)火,讓程序內(nèi)存降到了?3.89 - 0.98 = 2.91 G。
二:對(duì)GC代機(jī)制的理解
有不少程序員對(duì)gc中的代管理機(jī)制不是特別清楚,或者看過書之后理解也停留在理論上,沒法去驗(yàn)證書中所說,其實(shí)我也不是特別理解,????????????,作為一個(gè)準(zhǔn)備好好玩自媒體人,不能讓您白來一趟哈。
1. CLR堆模型
當(dāng)CLR不小心錯(cuò)入程序世界的時(shí)候,會(huì)給你分配兩個(gè)堆,一個(gè)叫做小對(duì)象堆,一個(gè)叫做大對(duì)象堆,默認(rèn)是以83k作為大小堆的分界線,當(dāng)然你也可以自定義配置,堆上的空間由很多的內(nèi)存段拼成的,可能你有點(diǎn)蒙,我畫張圖吧。
2. 對(duì)臨時(shí)內(nèi)存段的解釋
看完上圖,可能大家有兩個(gè)疑問:
<1> 為啥小對(duì)象堆中有一個(gè)臨時(shí)內(nèi)存段?
這是因?yàn)镃LR做了很多假設(shè),它假設(shè)在gen0和gen1上回收的對(duì)象會(huì)特別多,所以沒事就上去轉(zhuǎn)轉(zhuǎn),CLR為了方便GC快速清理回收壓縮。。。就將gen0和gen1都放置在這個(gè)臨時(shí)內(nèi)存段上。
你可能要問,有證據(jù)嗎???我就拿剛才的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: nonesegment 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 0x0000019a6da81000segment 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信息中可以看到小對(duì)象堆中目前有 15個(gè)內(nèi)存段, 大對(duì)象堆有2個(gè)內(nèi)存段, gen0的起始地址為0x0000019b0fc66b48,gen1的起始地址為0x0000019b0f73b138, 都落在了第15個(gè)內(nèi)存段內(nèi)?0000019b00e20000 0000019b00e21000 0000019b10047178 0xf226178(253911416),其余內(nèi)存段都被 gen2 占領(lǐng),如果大家有點(diǎn)亂,先多看幾遍,等一下看我的演示。
<2> 臨時(shí)內(nèi)存段大小是多少?
這個(gè)段的大小,需要看是x64還是x86機(jī)器,還要看GC是工作站模式還是服務(wù)器模式,不過msdn幫我們總結(jié)了,https://docs.microsoft.com/zh-cn/dotnet/standard/garbage-collection/fundamentals?, 截個(gè)圖給大家看一下。
我的本機(jī)是x64版本,工作站模式,可以通過?!eeversion?查看一下。
0:000> !eeversion 4.8.3801.0 free Workstation mode SOS Version: 4.8.3801.0 retail build對(duì)應(yīng)圖中,我的臨時(shí)內(nèi)存段的最大內(nèi)存是256M,再回過頭用4G程序的來驗(yàn)證一下內(nèi)存段大小,用?allocated - begin?即可。
ephemeral segment allocation context: nonesegment begin allocated size 0000019b00e20000 0000019b00e21000 0000019b10047178 0xf226178(253911416)0:000> ? 0000019b10047178 - 0000019b00e21000 Evaluate expression: 253911416 = 00000000`0f226178兩者差值為?253911416 byte => 242M?,可以看出離256M不遠(yuǎn)了,等到了256M又要觸發(fā)GC啦。。。。
3. 代機(jī)制簡(jiǎn)介
有了上面的基礎(chǔ),我覺得你對(duì)GC的gen機(jī)制應(yīng)該明白了,由于3個(gè)gen運(yùn)行時(shí)預(yù)定空間是隨GC觸發(fā)隨時(shí)變動(dòng),所以就不知道某個(gè)時(shí)刻各個(gè)gen當(dāng)時(shí)的空間觸發(fā)閾值。
接下來說一下三代的原理:當(dāng)gen0滿了會(huì)觸發(fā)GC回收,將gen0中活對(duì)象送到gen1中,死的就消滅掉,當(dāng)某時(shí)候gen1滿了,gen1的活對(duì)象會(huì)被送到gen2中,當(dāng)下個(gè)某一次gen2滿了,就向操作系統(tǒng)申請(qǐng)新的內(nèi)存段,所以你看到了4G程序占用了多達(dá)14個(gè)內(nèi)存段,就是這么一個(gè)道理,沒什么復(fù)雜的。
三:代機(jī)制原理的代碼演示
我剛才也說了,很多人知道這個(gè)理論,不知道怎么去驗(yàn)證,這里我就演示一下,先上代碼:
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("兩個(gè)對(duì)象已創(chuàng)建!雙雙進(jìn)入 Gen0");Console.Read();student1 = null;GC.Collect();Console.WriteLine("Student1 已從Gen0中抹掉,助力Student2上Gen1,是否繼續(xù)?");Console.ReadKey();GC.Collect();Console.WriteLine("再次助力Student2上Gen2");Console.ReadKey();Console.WriteLine("全部執(zhí)行結(jié)束!");Console.ReadLine();}}public class Student{public string UserName { get; set; }public string Email { get; set; }}代碼很簡(jiǎn)單,就是想讓你看一下student1和student2如何在gen0,gen1,gen2中游蕩,并且給你精準(zhǔn)找出來。
1. 探究 gen0 上的student1 和 studnet2
先啟動(dòng)程序,抓一下dump文件。
0:000> !clrstack -lConsoleApp4.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp4\Program.cs @ 18]LOCALS:0x000000017d7feeb8 = 0x000001d0962c2f280x000000017d7feeb0 = 0x000001d0962c2f480: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: nonesegment begin allocated size 000001d0962c0000 000001d0962c1000 000001d0962c7fe8 0x6fe8(28648) Large object heap starts at 0x000001d0a62c1000segment begin allocated size 000001d0a62c0000 000001d0a62c1000 000001d0a62c9a68 0x8a68(35432) Total Size: Size: 0xfa50 (64080) bytes. ------------------------------ GC Heap Size: Size: 0xfa50 (64080) bytes.仔細(xì)看上面的輸出,從主線程的堆棧上可以看到student1和studnet2的地址依次為0x000001d0962c2f28, 0x000001d0962c2f48,而gen0的起始地址為:0x000001d0962c1030,剛好落在 gen0 的區(qū)間內(nèi),可能你有點(diǎn)蒙,我畫一張圖。
2. 探究 student1 被消滅,student2進(jìn)入gen1
按下Enter鍵,執(zhí)行后續(xù)代碼將student1=null,再執(zhí)行GC操作,看下堆中又是如何?
0:000> !clrstack -l ConsoleApp4.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp4\Program.cs @ 24]LOCALS:0x000000607e9fea50 = 0x00000000000000000x000000607e9fea48 = 0x0000017f0dff2f38000000607e9fec88 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: nonesegment begin allocated size 0000017f0dff0000 0000017f0dff1000 0000017f0dff8eb8 0x7eb8(32440) Large object heap starts at 0x0000017f1dff1000segment begin allocated size 0000017f1dff0000 0000017f1dff1000 0000017f1dff9a68 0x8a68(35432) Total Size: Size: 0x10920 (67872) bytes. ------------------------------ GC Heap Size: Size: 0x10920 (67872) bytes.如果弄明白了上一個(gè)案例,看這里就很簡(jiǎn)單了,很清楚的看到studnet2落在了gen1區(qū)間段,不過從起始地址上看,gen1的空間變大了。。。我繼續(xù)畫一張圖。
3. 探究student2 送上了 gen2
0:000> !clrstack -l ConsoleApp4.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp4\Program.cs @ 28]LOCALS:0x000000d340bfebb0 = 0x00000000000000000x000000d340bfeba8 = 0x00000217b5df2f38000000d340bfede8 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: nonesegment begin allocated size 00000217b5df0000 00000217b5df1000 00000217b5df8f58 0x7f58(32600) Large object heap starts at 0x00000217c5df1000segment begin allocated size 00000217c5df0000 00000217c5df1000 00000217c5df9a68 0x8a68(35432) Total Size: Size: 0x109c0 (68032) bytes. ------------------------------ GC Heap Size: Size: 0x109c0 (68032) bytes.很簡(jiǎn)單,我就不畫圖了哈,student2的內(nèi)存地址可是落在 gen2上哦~????????????
四:總結(jié)
GC.Collect盡量少用,省的把內(nèi)部的分配和回收算法搞亂了,非要用的話也要理解之后再根據(jù)自己的場(chǎng)景使用哈。
本篇就說到這里,希望對(duì)你有幫助
總結(jié)
以上是生活随笔為你收集整理的内存迟迟下不去,可能你就差一个GC.Collect的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 麻雀虽小,五脏俱全
 - 下一篇: 服务器程序的Xamarin-Java.I