C# 垃圾回收器高效工作
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
首先我會(huì)專(zhuān)注于Workstation GC(因此所有的數(shù)字都是工作站GC的)。然后我會(huì)談?wù)劰ぷ髡綠C和服務(wù)器GC之間的區(qū)別(有時(shí)候你沒(méi)有必要選擇,稍后我會(huì)解釋為什么)。
代:
把托管堆上的對(duì)象分成3代是為了調(diào)優(yōu)垃圾回收的性能,大多數(shù)對(duì)象都在0代時(shí)消亡。例如:在一個(gè)服務(wù)器程序中,處理每個(gè)請(qǐng)求相關(guān)的對(duì)象,都會(huì)在請(qǐng)求完 成后消亡。本質(zhì)上1代對(duì)象是在新分配對(duì)象和常駐內(nèi)存之間的一個(gè)緩沖區(qū)。當(dāng)你在性能計(jì)數(shù)器中觀察2代回收發(fā)生的次數(shù)比0代回收次數(shù)要少的多。而1代回收次數(shù) 相對(duì)來(lái)說(shuō)不是很重要,回收1代對(duì)象比回收0代對(duì)象的代價(jià)高的不是很多。而回收2代對(duì)象就意味著要掃描整個(gè)托管堆了,代價(jià)相對(duì)要大得多。
GC段(segment):
首先讓我們看一下GC是如何向操作系統(tǒng)申請(qǐng)內(nèi)存的。GC以段的方式保留內(nèi)存。每一個(gè)段是16M(服務(wù)器模式下可能是64M)。當(dāng)執(zhí)行引擎啟動(dòng)時(shí),我們保留初始的GC段,一個(gè)給小對(duì)象用,另一個(gè)段給大對(duì)象用。有關(guān)大對(duì)象堆的垃圾回收請(qǐng)參考這里
在需要的時(shí)候可以向操作系統(tǒng)申請(qǐng)更多內(nèi)存,或者交還給操作系統(tǒng)。當(dāng)所有段都用完之后我們就申請(qǐng)一個(gè)新段。在每一次完整的垃圾回收之后多余的段會(huì)交還給操作系統(tǒng)。
大對(duì)象有自己的段,垃圾回收器對(duì)大對(duì)象的處理方式和小對(duì)象是不一樣的所以大對(duì)象不和小對(duì)象共享段。
分配:
當(dāng)你在托管堆上分配一個(gè)對(duì)象時(shí),要付出什么代價(jià)呢?如果我們不考慮回收的話(huà),有兩點(diǎn)1是向前移動(dòng)指針,2是為新對(duì)象清空內(nèi)存。而對(duì)于實(shí)現(xiàn)Finalize的方法的對(duì)象還需要把對(duì)象的指針?lè)诺浇K結(jié)隊(duì)列中。
注意我說(shuō)的是“如果我們不考慮回收”—這意味著分配的代價(jià)和對(duì)象的大小成正比。申請(qǐng)的越少,GC的代價(jià)就越小。如果你需要15個(gè)byte,就申請(qǐng) 15個(gè)字節(jié);不要像使用maalloc一樣申請(qǐng)32個(gè)字節(jié)。有一個(gè)閥值,當(dāng)超過(guò)這個(gè)值時(shí),就會(huì)觸發(fā)垃圾回收。你要盡可能少的觸發(fā)垃圾回收。
GC堆和NT堆還有一點(diǎn)不同:分配對(duì)象的時(shí)間越接近,對(duì)象在GC堆上的也越接近。
在GC堆上分配的每一個(gè)對(duì)象都需要額外的8byte的開(kāi)銷(xiāo),4byte用來(lái)同步,4byte存放方法表指針。
回收:
首先我們要知道什么時(shí)候觸發(fā)回收? 有如下三種情況會(huì)觸發(fā):
1. 分配時(shí)超過(guò)了0代堆的閥值
2. 調(diào)用了GC.Collect()方法
3. 操作系統(tǒng)給應(yīng)用程序發(fā)出低內(nèi)存信號(hào)
第1種情況是最典型的觸發(fā)原因,當(dāng)分配的對(duì)象足夠多時(shí),就會(huì)觸發(fā)0代堆的垃圾回收。在每一次回收之后,0代堆就清空了。然后繼續(xù)分配對(duì)象,0代堆填滿(mǎn)之后就會(huì)觸發(fā)下一次回收。
你要盡量避免第2種情況,這個(gè)很簡(jiǎn)單,不要在程序代碼中調(diào)用GC.Collect方法就可以了。通常情況下你不應(yīng)該調(diào)用Collect方法。BCL is basically the only place that should call this (in very limited places);當(dāng)你在程序中調(diào)用GC.Collect方法時(shí),性能會(huì)降低,因?yàn)榛厥仗崆皥?zhí)行了,而垃圾回收器執(zhí)行回收的調(diào)度是經(jīng)過(guò)算法優(yōu)化的。
第3種情況受操作系統(tǒng)上運(yùn)行的其他程序影響,這個(gè)你的程序沒(méi)法控制,你只能盡可能的優(yōu)化好你的程序或模塊。
讓我們談一下這意味著什么。首先,托管堆是程序的工作集的一部分。它會(huì)消耗私有頁(yè)。在理想情況下,所有對(duì)象都在0代時(shí)消亡(這意味著,幾乎所有對(duì)象 都在0代回收,完全回收從不會(huì)發(fā)生)因此,你的GC堆永遠(yuǎn)不會(huì)超過(guò)0代堆的大小。而事實(shí)上這種情況是不可能的,因此,你真的需要保證托管堆的大小是可控 的。
第二,你需要保證垃圾回收消耗的時(shí)間資源是可控的。這個(gè)意思是一要盡可能少觸發(fā)GC,二盡可能少發(fā)生高代的GC。一次高代的回收要比底一代回收的代價(jià)高得多,因?yàn)楦叽幕厥找獟呙韪嗟膶?duì)象,要同時(shí)執(zhí)行所有更低代的回收。
CLRProfiler是一個(gè)觀察GC堆看堆上的對(duì)象被那個(gè)對(duì)象引用的工具,它非常棒。
如何組織你的數(shù)據(jù):
1) 用值類(lèi)型還是引用類(lèi)型
如你所知,值類(lèi)型數(shù)據(jù)是存放在棧上的,而引用類(lèi)型對(duì)象是存在托管堆上的。因此,人們會(huì)問(wèn),如何決定什么時(shí)候使用值類(lèi)型,什么使用引用類(lèi)型呢。值類(lèi)型 不會(huì)觸發(fā)垃圾回收,但是如果你的值類(lèi)型經(jīng)常做裝箱操作,裝箱操作要比剛開(kāi)始就創(chuàng)建一個(gè)引用類(lèi)型對(duì)象要昂貴的多;當(dāng)值類(lèi)型對(duì)象作為參數(shù)傳遞時(shí)需要復(fù)制一份。 但是如果你的引用類(lèi)型只有一個(gè)小成員如果做成引用類(lèi)型的話(huà),還需要額外4字節(jié)的指針開(kāi)銷(xiāo)和同步開(kāi)銷(xiāo)以及方法表開(kāi)銷(xiāo)。因此該使用值類(lèi)型還是引用類(lèi)型是由類(lèi)型 本身決定的。
2) 富引用對(duì)象(Reference rich object)
如果一個(gè)對(duì)象是富引用的,會(huì)給分配和回收都帶拉壓力。每一個(gè)內(nèi)嵌的對(duì)象都需要8字節(jié)的額外開(kāi)銷(xiāo)。因?yàn)榉峙涞拈_(kāi)銷(xiāo)和對(duì)象的大小是成正比的,所以開(kāi)銷(xiāo)就大了一些。另外富引用會(huì)導(dǎo)致構(gòu)建對(duì)象圖的時(shí)間增大,增加了回收的開(kāi)銷(xiāo)。
因此我建議你設(shè)計(jì)對(duì)象時(shí)只設(shè)計(jì)必要的字段,如果對(duì)另外一個(gè)引用類(lèi)型的強(qiáng)引用不是必須的,就不要引用它。你應(yīng)該盡量避免讓已存在很長(zhǎng)時(shí)間的對(duì)象引用新分配的對(duì)象。
3) 可終結(jié)對(duì)象(實(shí)現(xiàn)Finalize方法的對(duì)象)
如垃圾回收原理1中所述終結(jié)對(duì)象會(huì)延長(zhǎng)回收的時(shí)間,不僅延長(zhǎng)可終結(jié)對(duì)象本身的,還會(huì)延長(zhǎng)它的引用鏈下游的所有對(duì)象的回收時(shí)間。所以如果對(duì)象必須是可終結(jié)的,你就要盡可能的隔離它,不讓它引用其他對(duì)象
4) 對(duì)象的存儲(chǔ)位置:
當(dāng)你為一個(gè)對(duì)象的子對(duì)象分配空間時(shí),你最好在同一時(shí)間分配父對(duì)象和子對(duì)象,這樣父子對(duì)象在托管堆上的地址就會(huì)在一起,回收起來(lái)也會(huì)一起回收,回收的效率就會(huì)相對(duì)高一些。
大對(duì)象:
當(dāng)一個(gè)對(duì)象占用的內(nèi)存超過(guò)85,000bytes時(shí)它就會(huì)被分配到LOH上。SOH段永遠(yuǎn)都不會(huì)做移動(dòng)—而只是清空對(duì)象(使用一個(gè)空的鏈表)。但是這個(gè)情況是一種實(shí)現(xiàn)的細(xì)節(jié),你不應(yīng)該依賴(lài)這個(gè)實(shí)現(xiàn)細(xì)節(jié)。如果你分配了一個(gè)大對(duì)象,不希望他發(fā)生移動(dòng),那么你應(yīng)該fix它。
只有在2代回收時(shí)才會(huì)做大對(duì)象的回收,而2代回收的代價(jià)是很大的。有時(shí)候你會(huì)發(fā)現(xiàn)2代回收之后2代堆的大小并沒(méi)有發(fā)生多大變化,這有可能是因?yàn)榇髮?duì)象堆大小超過(guò)閥值觸發(fā)了2代回收。
一個(gè)好的實(shí)踐:分配一個(gè)大對(duì)象然后重復(fù)利用它。如果說(shuō)你需要一個(gè)100k或者120k的大對(duì)象,你應(yīng)該申請(qǐng)一個(gè)120k的然后重復(fù)利用它。多次分配臨時(shí)大對(duì)象可能會(huì)觸發(fā)2代回收,對(duì)性能會(huì)有負(fù)面影響。
這篇文章我們談?wù)凣C的不同工作模式,以及各個(gè)模式如何工作和他們之間的不同,讓你明白你的應(yīng)用程序該如何選擇工作模式。
迄今為止運(yùn)行時(shí)GC工作模式:
1)關(guān)閉并發(fā)的工作站GC
2)開(kāi)啟并發(fā)的工作站GC
3)服務(wù)器GC
如果你在寫(xiě)一個(gè)獨(dú)立的托管程序并且沒(méi)有做任何配置,你使用的GC工作模式是開(kāi)啟并發(fā)的工作站GC。這一點(diǎn)多數(shù)人可能會(huì)感到驚訝,因?yàn)槲覀兊奈臋n中并 沒(méi)有提起并發(fā)GC,有時(shí)候會(huì)把并發(fā)GC稱(chēng)為”background GC”(while referring working GC as “foreground GC”).
如果你的程序在宿主程序中運(yùn)行,宿主可能會(huì)為程序選擇GC的工作模式。
需要注意的是:如果你的程序配置成服務(wù)器GC,你的程序運(yùn)行在一臺(tái)高性能的機(jī)器上,實(shí)際上你的服務(wù)器是運(yùn)行在“關(guān)閉并發(fā)的工作GC”模式下。因?yàn)椤瓣P(guān)閉并發(fā)的工作站GC”為高性能服務(wù)器的大吞吐量服務(wù)做了優(yōu)化。
各個(gè)GC模式的設(shè)計(jì)目標(biāo):
1) 關(guān)閉并發(fā)的工作站GC為高性能服務(wù)器的高吞吐量做了優(yōu)化。我們?cè)诶厥諘r(shí)根據(jù)分配和復(fù)活模式做動(dòng)態(tài)調(diào)優(yōu)因此可以程序運(yùn)行時(shí)自動(dòng)調(diào)優(yōu)GC的工作效率。
2) 開(kāi)啟并發(fā)的工作站GC是為要求精確響應(yīng)時(shí)間的交互式應(yīng)用程序設(shè)計(jì)的。開(kāi)啟并發(fā)使垃圾回收造成的工作進(jìn)程暫停時(shí)間縮短。 這個(gè)目的是用一些內(nèi)存和CPU換來(lái)的,因此在這種模式下垃圾回收需要做的工作略多一點(diǎn)需要的回收時(shí)間會(huì)略長(zhǎng)一些。
3) 服務(wù)器GC,從名字上我們可以看出這種工作模式是為服務(wù)器應(yīng)用程序設(shè)計(jì)的;典型的場(chǎng)景是你有一個(gè)工作線(xiàn)程池這些線(xiàn)程坐著相似的處理。例如:做處理同樣的請(qǐng) 求或者處理相同類(lèi)型的事務(wù)。所有的線(xiàn)程使用幾乎相同的分配模式。服務(wù)器GC是為要求高吞吐量的和高擴(kuò)展性的多處理器服務(wù)器設(shè)計(jì)的
各個(gè)GC模式如何工作:
讓我們從關(guān)閉并發(fā)的工作站GC說(shuō)起,其執(zhí)行流程如下:
1) 一個(gè)托管進(jìn)程做內(nèi)存分配
2) 分配完所有可用的內(nèi)存(我會(huì)解釋這是什么意思)
3) 觸發(fā)了垃圾回收,垃圾回收操作在做分配的線(xiàn)程上運(yùn)行
4) GC調(diào)用SuspendEE來(lái)掛起所有的托管線(xiàn)程
5) GC開(kāi)始工作
6) GC調(diào)用RestartEE來(lái)重啟工作托管進(jìn)程
7) 托管進(jìn)程繼續(xù)運(yùn)行
你可以看到在第5步中所有的托管線(xiàn)程都停止執(zhí)行來(lái)等待垃圾回收完成工作。SuspendEE不會(huì)掛起本地線(xiàn)程(native threads)。
GC中的每一代都有一個(gè)“分配預(yù)算”的概念。每一代的“分配預(yù)算”在運(yùn)行時(shí)是動(dòng)態(tài)調(diào)整的。因?yàn)槲覀兘?jīng)常在0代上做分配,你可以想象0代預(yù)算超支了,這樣就會(huì)觸發(fā)垃圾回收。這里的預(yù)算和GC堆的段大小完全不是一回事,預(yù)算比段大小要小得多。
CLR垃圾回收可以做內(nèi)存移動(dòng)也可以不做移動(dòng)。不做移動(dòng)時(shí)也稱(chēng)為“清掃”,清掃的代價(jià)要比做壓縮的代價(jià)低一些,因?yàn)樗恍枰獜?fù)制移動(dòng)內(nèi)存。
在開(kāi)啟并發(fā)垃圾回收(Concurrent GC)時(shí),最大的差異是掛起和重啟。 我前面提到并發(fā)GC允許更少的暫停時(shí)間。因此開(kāi)啟并發(fā)的垃圾回收會(huì)盡可能少的執(zhí)行垃圾回收,執(zhí)行時(shí)間也非常短。在剩余的時(shí)間中如果需要托管線(xiàn)程可以運(yùn)行和 分配內(nèi)存。開(kāi)啟并發(fā)時(shí)我們會(huì)在開(kāi)始時(shí)給0代一個(gè)很大的分配預(yù)算來(lái)保證垃圾回收運(yùn)行期間有足夠的空間分配對(duì)象。盡管如此,如果在并發(fā)回收運(yùn)行中托管線(xiàn)程需要 分配過(guò)多的內(nèi)存,線(xiàn)程也會(huì)被堵塞直到回收完成。
記住0代和1代回收非???#xff0c;所以在做0,1代回收時(shí)是不會(huì)做并發(fā)回收的。我們只是在2代回收時(shí)才會(huì)并發(fā)回收。如果我們決定做2代回收,我們會(huì)決定是否并發(fā)回收。
服務(wù)器垃圾回收,這種模式和前兩種完全不同。我們會(huì)為每一個(gè)CPU創(chuàng)建一個(gè)回收線(xiàn)程。垃圾回收在這些線(xiàn)程上執(zhí)行而不是在分配線(xiàn)程上,其工作流程如下:
1. 一個(gè)托管線(xiàn)程做回收
2. 分配達(dá)到閥值
3. 給GC線(xiàn)程發(fā)信號(hào),讓GC線(xiàn)程做垃圾回收,等待回收結(jié)束
4. GC線(xiàn)程運(yùn)行,結(jié)束時(shí)發(fā)出回收完成的信號(hào)(在回收過(guò)程中,所有的托管線(xiàn)程會(huì)像工作站模式中一樣被掛起)
5. 托管線(xiàn)程收到信號(hào)重新開(kāi)始運(yùn)行
如果配置各個(gè)垃圾回收模式:
要關(guān)閉并發(fā)回收,在配置文件中添加下面配置項(xiàng):
| <configuration> <runtime> <gcConcurrentenabled="false"/> </runtime> </configuration> |
要使用服務(wù)器GC,使用下面配置:
?| <configuration> <runtime> <gcServerenabled=“true"/> </runtime> </configuration> |
關(guān)于這兩個(gè)配置節(jié)可以參考msdn。
這篇文章我們談?wù)劰潭▽?duì)象的內(nèi)存地址(pinning)和弱引用……這兩個(gè)和垃圾回收處理密切相關(guān)的東西。
固定對(duì)象的內(nèi)存地址:
固定對(duì)象的內(nèi)存地址和實(shí)現(xiàn)Finalize方法的對(duì)象有一點(diǎn)是相同的 …… 兩者都是因?yàn)槲覀兊某绦虿坏貌缓捅镜卮a打交道。
怎么固定對(duì)象的內(nèi)存地址呢?有三種方法
1. 使用GCHandle的靜態(tài)方法Alloc(object val,GCHandleType type) ,將type值設(shè)為GCHandleType.Pinned
2. 在C#中使用fixed關(guān)鍵字
3. 在調(diào)用本地代碼時(shí),本本地代碼固定(例如:to marshal LPWSTR as a String object, Interop pins the buffer for the duration of the call)
對(duì)于小對(duì)象堆來(lái)說(shuō),在代碼中固定對(duì)象地址是導(dǎo)致內(nèi)存碎片的唯一原因,如果沒(méi)有固定內(nèi)存地址的對(duì)象那么在小對(duì)象堆中就不應(yīng)該有碎片。
而對(duì)于大對(duì)象堆,固定內(nèi)存地址的操作是無(wú)效的,因?yàn)楝F(xiàn)在的垃圾回收機(jī)制是不會(huì)移動(dòng)大對(duì)象堆的對(duì)象的。不過(guò),這一點(diǎn)只是GC的內(nèi)部實(shí)現(xiàn),你不應(yīng)該依賴(lài)于這個(gè)實(shí)現(xiàn),如果大對(duì)象需要固定內(nèi)存地址,你還是要寫(xiě)固定需要的代碼。
內(nèi)存碎片從來(lái)都不是一個(gè)好東西。它會(huì)增加垃圾回收工作的難度 —— 如果沒(méi)有固定內(nèi)存地址的對(duì)象,垃圾回收器在移動(dòng)內(nèi)存時(shí)只需要將非垃圾對(duì)象覆蓋空閑內(nèi)存就可以了,而堆上存在固定地址的對(duì)象,垃圾回收器就不得不在移動(dòng)中考慮,不覆蓋這類(lèi)對(duì)象,也不能移動(dòng)它們。
那么如何才能知道你的程序中有多少內(nèi)存碎片呢?你可以使用!dumpheap命令:“dumpheap –type Free -stat”會(huì)給出所有釋放對(duì)象占用內(nèi)存的統(tǒng)計(jì)信息。 通常情況下如果碎片大小占總大小的比例小于10%的話(huà),就沒(méi)什么可擔(dān)憂(yōu)的。因此如果你看到釋放對(duì)象的絕對(duì)數(shù)很大,但是總數(shù)少于10%就沒(méi)必要害怕。
如果你確實(shí)需要固定對(duì)象的地址,請(qǐng)注意下面幾件事情:
1. 短時(shí)間的固定開(kāi)銷(xiāo)會(huì)很小
“短時(shí)間”,多短算短呢? 如果固定內(nèi)存地址的對(duì)象在垃圾回收之前就成為垃圾對(duì)象了,那么這個(gè)時(shí)間就是足夠短了。因?yàn)楣潭▋?nèi)存其實(shí)就是在對(duì)象頭置一個(gè)bit位,如果在對(duì)象存活期沒(méi)有 發(fā)生垃圾回收,那么就沒(méi)有額外開(kāi)銷(xiāo)。如果在垃圾回收發(fā)生后這個(gè)對(duì)象還活著,垃圾回收器在移動(dòng)內(nèi)存時(shí)就得做更多的計(jì)算保證不會(huì)移動(dòng)此對(duì)象,也不會(huì)覆蓋它。
2. 固定老對(duì)象的代價(jià)會(huì)比固定年輕對(duì)象的代價(jià)要小一些
何為“老對(duì)象”呢?是指經(jīng)過(guò)兩次垃圾回收,已經(jīng)被遷移到2代堆的對(duì)象;這時(shí)候?qū)ο蟮乃诘膬?nèi)存區(qū)域已經(jīng)相對(duì)穩(wěn)定了。造成內(nèi)存碎片的可能性會(huì)小一些。
兩個(gè)固定對(duì)象內(nèi)存地址好實(shí)踐:
1. 在大對(duì)象堆上分配固定地址的對(duì)象,每次使用使使用其中的一部分
這樣做的優(yōu)點(diǎn)是顯而易見(jiàn)的,大對(duì)象堆不會(huì)做內(nèi)存移動(dòng)操作,所以就不存在因?yàn)楣潭▽?duì)象地址導(dǎo)致的開(kāi)銷(xiāo)了;缺點(diǎn)是沒(méi)有現(xiàn)成的API來(lái)把大對(duì)象分成一小塊一小塊使用,這需要開(kāi)發(fā)人員按需編碼使用。
2. 分配一個(gè)小對(duì)象的緩沖池,(and then hand them out when needed)
例如,我有一個(gè)緩沖池,方法M有一個(gè)byte[]數(shù)組需要固定內(nèi)存地址。如果這個(gè)數(shù)組已經(jīng)是2代對(duì)象了,就固定它。而如果緩沖區(qū)不需要使用很長(zhǎng)時(shí)間,那么就在0代和1代回收時(shí)回收它。這樣所有在緩沖池中的對(duì)象就都是2代對(duì)象了。
void M(byte[] b)
{
if (GC.GetGeneration(b) == GC.MaxGeneration)
{
RealM(b);
return;
}
// GetBuffer will allocate one if no buffers
// are available in the buffer pool.
byte[] TempBuffer = BufferPool.GetBuffer();
RealM(TempBuffer);
CopyBackToUserBuffer(TempBuffer, b);
BufferPool.Release(TempBuffer);
}
弱引用:
弱引用是如何實(shí)現(xiàn)的呢?
一個(gè)弱引用對(duì)象有托管和非托管兩個(gè)部分。托管部分是WeakReference對(duì)象本身。在它的構(gòu)造函數(shù)中我們創(chuàng)建一個(gè)GC句柄,它是非托管的部分 —— 這會(huì)在AppDomain的句柄表中插入一項(xiàng)(GCHandle類(lèi)的Alloc方法都是這么做的,只不過(guò)是將不同類(lèi)型插入到各自的表中)。當(dāng)弱引用指向的 對(duì)象沒(méi)有強(qiáng)引用時(shí)就會(huì)被垃圾回收器回收掉。因?yàn)閃eakReference對(duì)象本身是一個(gè)托管對(duì)象,所以它沒(méi)有強(qiáng)引用時(shí)也會(huì)被回收。
弱引用沒(méi)必要引用小對(duì)象:
如果你有一個(gè)非常小的對(duì)象,比如說(shuō)一個(gè)DWORD字段,對(duì)象的大小是12byte(12byte是對(duì)象的最小尺寸)。而WeakReference 對(duì)象有一個(gè)IntPtr和一個(gè)bool字段,而GC句柄是一個(gè)指針的大小(32位機(jī)器4byte,64位機(jī)器8byte);這就是說(shuō)你需要使用15個(gè) byte的對(duì)象來(lái)延長(zhǎng)一個(gè)12byte對(duì)象的長(zhǎng)度,這是不劃算的。顯然你不應(yīng)該創(chuàng)建很多弱引用對(duì)象來(lái)引用一些小對(duì)象。
那么,弱引用有什么用呢?
弱引用的作用是在垃圾回收發(fā)生之前即便對(duì)象上沒(méi)有強(qiáng)引用,也可以再次使用該對(duì)象。如果執(zhí)行垃圾回收它會(huì)被回收。
為什么要使用弱引用來(lái)跟蹤一個(gè)對(duì)象的釋放,而不是用Finalizer方法呢? 使用弱引用的優(yōu)點(diǎn)是跟蹤的對(duì)象不會(huì)被推遲到下次垃圾回收時(shí)才真正的被回收;缺點(diǎn)是需要消耗一點(diǎn)內(nèi)存,只有當(dāng)用戶(hù)代碼檢查弱引用指向的對(duì)象為null時(shí)才清除對(duì)象。
下面是兩個(gè)弱對(duì)象的使用實(shí)例:
Option A):
| classA { WeakReference _target; MyObject Target { set { _target =newWeakReference(value); } get { Object o = _target.Target; if(o !=null) { returno; } else { // my target has been GC'd - clean up Cleanup(); returnnull; } } } voidM() { // target needs to be alive throughout this method. MyObject target = Target; if(target ==null) // target has been GC'd, don't bother return; else { // always need target to be alive. DoSomeWork(); } GC.KeepAlive(target); } } |
Option B):
?| classA { WeakReference _target; MyObject ShortTemp; MyObject Target { set { _target =newWeakReference(value); } get { Object o = _target.Target; if(o !=null) { returno; } else { // my target has been GC'd - clean up Cleanup();?????? returnnull; } } } voidM() { // target needs to be alive throughout this method. MyObject target = Target; ShortTemp = target; if(target ==null) { // target has been GC'd, don't bother return; } else { // could assert that ShortTemp is not null. DoSomeWork(); } ShortTemp =null; } } |
使用弱對(duì)象維護(hù)緩存:
你可以創(chuàng)建一個(gè)WeakReference指向?qū)ο蟮臄?shù)組
WeakReferencesToObjects WeakRefs[];
數(shù)組中的每個(gè)元素都是弱對(duì)象指向的對(duì)象。我們可以定期的遍歷這個(gè)數(shù)組看哪個(gè)對(duì)象已經(jīng)死了然后釋放引用此對(duì)象的WeakReference對(duì)象。
如果我們經(jīng)常處理已經(jīng)釋放的對(duì)象,緩存就會(huì)在每次垃圾回收之后失效。
如果對(duì)你來(lái)說(shuō)這還不夠,你可以使用二級(jí)緩存機(jī)制。
維持一段時(shí)間的緩存項(xiàng)的強(qiáng)引用列表,在這段時(shí)間之后,將強(qiáng)引用轉(zhuǎn)換成弱引用,弱引用意味著這些項(xiàng)將要被剔除。
你可能有不同的策略來(lái)處理緩存,比如根據(jù)緩存被讀取次數(shù)——讀取次數(shù)少的項(xiàng)被轉(zhuǎn)換成弱引用;或者根據(jù)緩存項(xiàng)的個(gè)數(shù),如果緩存項(xiàng)數(shù)超過(guò)某個(gè)數(shù)字就把超過(guò)的緩存項(xiàng)設(shè)置為弱引用。這取決于你的應(yīng)用。調(diào)優(yōu)緩存是另一個(gè)完整的主題——或許將來(lái)我會(huì)寫(xiě)一下。
轉(zhuǎn)載于:https://my.oschina.net/dgwutao/blog/139422
總結(jié)
以上是生活随笔為你收集整理的C# 垃圾回收器高效工作的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: AMD R7 7840H 处理器为中国专
- 下一篇: c# char unsigned_dll