溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何理解Dotnet的垃圾回收

發(fā)布時間:2021-10-18 11:56:00 來源:億速云 閱讀:106 作者:iii 欄目:web開發(fā)

本篇內(nèi)容介紹了“如何理解Dotnet的垃圾回收”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

在說垃圾回收之前,先說說兩個概念:

  • 托管代碼,是由CLR管理的代碼

  • 非托管代碼,是由操作系統(tǒng)直接執(zhí)行的代碼

在早期C++的時候,內(nèi)存分配和釋放都是由我們手動處理的,而在公共語言進(jìn)行時CLR中,多了一個垃圾收集器GC,來充當(dāng)自動內(nèi)存管理器,完成同樣的工作。從此,對于開發(fā)人員來說,我們可以不需要用顯式的代碼來執(zhí)行內(nèi)存管理。這樣做的好處是明顯的:大量相關(guān)內(nèi)存的錯誤被消除了,比方?jīng)]有釋放對象導(dǎo)致的內(nèi)存泄露,或試圖訪問已經(jīng)釋放的對象的內(nèi)存,等等。

一、回收和管理托管資源

上面說了,垃圾回收GC在Dotnet中是一個自動的內(nèi)存管理器,是一種機(jī)制,用來清理和回收堆內(nèi)存中未引用的部分。

通常CLR會在這些情況下啟動垃圾回收:

  • 需要在堆上分配內(nèi)存給一個新對象,但沒有足夠的空閑內(nèi)存時;

  • 對象被強制Dispose時;

  • 托管堆上已分配對象的內(nèi)存超過了閥值(這個閥值會動態(tài)調(diào)整);

  • 調(diào)用了GC.Collect方法

這些內(nèi)容都是基礎(chǔ),了解了非常好,面試時有話可說。不了解也沒關(guān)系,不會影響做一個好的程序出來。

下面的內(nèi)容如果能記住,倒是對于程序開發(fā)很有幫助。

在Dotnet的垃圾回收機(jī)制中,回收器會自行優(yōu)化并適用于多種方案。但是,我們?nèi)匀豢梢愿鶕?jù)運行環(huán)境來設(shè)置垃圾回收的類型。

Dotnet的CLR提供了下面兩種類型的垃圾回收:

這兩種回收機(jī)制,有一定的區(qū)別。

工作站回收,主要是為客戶端應(yīng)用設(shè)計的,也是程序默認(rèn)的回收機(jī)制。垃圾回收的過程,跑在觸發(fā)垃圾回收的用戶線程上,并使用相同的優(yōu)先級。這種方式,優(yōu)點是不會被掛起或延遲,缺點是需要與其它線程競爭CPU時間。當(dāng)運行環(huán)境中只有一個CPU時,系統(tǒng)會自動采用工作站方式,不管你設(shè)置成什么。

服務(wù)器回收,針對的是高吞吐的服務(wù)器應(yīng)用,回收過程跑在專用的高優(yōu)先級線程上,而且默認(rèn)是多線程在跑,所以效率更高,缺點是占用的資源會更多,而且由于線程之間的干擾和上下文切換,會影響整體性能。

所以,選擇什么樣的回收機(jī)制,需要認(rèn)真分析。通常普通應(yīng)用,工作站回收就好。如果是服務(wù)器端的API服務(wù),需要選擇服務(wù)器回收。而如果是在服務(wù)端需要啟動多個實例進(jìn)行處理,比方對總線的數(shù)據(jù)保存,那還是工作站回收好。

設(shè)置垃圾回收方式,在開發(fā)時,可以在xxx.csproj文件中加入:

<PropertyGroup>    <ServerGarbageCollection>true</ServerGarbageCollection>  </PropertyGroup>

其中,設(shè)置true就是服務(wù)器模式,設(shè)置false就是工作站模式,當(dāng)然,去掉這一行,默認(rèn)也是工作站模式。

對于生產(chǎn)環(huán)境中已經(jīng)上線的應(yīng)用,也可以修改回收模式。找到程序目錄中的xxx.runtimeconfig.json文件,在里面加入:

"configProperties": {   "System.GC.Server": true }

這兩個配置的關(guān)系是:如果開發(fā)時在.csproj中加入了ServerGarbageCollection,那在發(fā)布時會自動在.runtimeconfig.json中加入System.GC.Server。

二、回收和管理非托管資源

上面說到的回收機(jī)制,針對的是托管資源。

對于非托管資源,GC不會主動進(jìn)行回收。回收非托管資源,只能手工編寫代碼并顯式的釋放。

通常來說,程序中用到的操作系統(tǒng)的資源文件、網(wǎng)絡(luò)或數(shù)據(jù)庫連接等,都屬于非托管資源,需要手工清理。

有兩種方法可以清理非托管理資源:

  • 使用終結(jié)器Finalize,并由GC回收

  • 手動處理Dispose

2.1 使用終結(jié)器Finalize

終結(jié)器Finalize是System.Object的一個虛方法,這個方法在GC回收對象的內(nèi)存之前由垃圾回帳調(diào)用。我們可以重寫這個方法,來釋放非托管資源。

多說兩句:似乎MS對這個部分有些猶豫,所以這兒規(guī)則一直處在兩可之間。C#在析構(gòu)函數(shù)的支持上并不嚴(yán)格。System.Object支持重寫Object.Finalize方法,但它創(chuàng)建的類卻不支持,重寫會報錯,而只能通過改寫析構(gòu)函數(shù)來實現(xiàn),并由編譯器將代碼包裝在try塊中的析構(gòu)函數(shù)或重寫的Finalize中,并由finally調(diào)用Object.Finalize來實現(xiàn)。

使用終結(jié)器,缺點也是比較明顯的。GC檢測到一個對象需要回收時,會在一段不確定的時間之后調(diào)用終結(jié)器。這個不確定很討厭,我們很難預(yù)料什么時候?qū)ο蟊粚嶋H釋放。

Finalize雖然看著是手動清除非托管資源,其實還是由垃圾回收器去做的。它的最大作用是確保非托管資源一定被釋放。

2.2 手動處理Dispose

手動處理最重要的理由,是在需要的時候立即釋放,而不是讓垃圾回收器進(jìn)行不確定延時后的釋放。

手動釋放,主要的工作是提供一個IDisposable.Dispose的實現(xiàn),來實現(xiàn)非托管資源的確定性釋放。這樣,當(dāng)需要釋放時,調(diào)用Dispose方法,就會立即釋放非托管資源。

手動處理實現(xiàn)起來很簡單??蚣芴峁┝艘粋€接口System.IDisposable:

public interface IDisposable   {       void Dispose();   }

他只包含一個方法Dispose。使用時,需要實現(xiàn)這個方法,在使用完成后及時釋放非托管資源。

同時,Dispose方法還提供了GC.SuppressFinalize方法,來告訴GC對象已經(jīng)被手動處理,不再需要調(diào)用終結(jié)器。

public void Dispose()   {       GC.SuppressFinalize(this);   }

這種方式下,對象的內(nèi)存可以做到提前回收。

在某些情況下,可能無法調(diào)用IDisposable.Dispose方法來釋放非托管資源,但場景下又確實需要確定性地釋放,這時候可能通過重寫Object.Finalize來實現(xiàn):

public class MyClass     {        ~MyClass()        {       //TODO: 釋放未托管的資源    }     }

有點奇怪,是不是?

其實,這就是上邊我說MS猶豫的地方。如果你直接重寫Object.Finalize,像下面這樣:

public class MyClass     {        protected override void Finalize()        {           //TODO: 釋放未托管的資源    }     }

編譯時會報錯Do not override object.Finalize. Instead, provide a  destructor.,而他正確的寫法,就是析構(gòu)函數(shù)。

上面說的內(nèi)容,做成一個套路模板,就會是這樣的:

public class MyClass : IDisposable {     private bool disposedValue;      protected virtual void Dispose(bool disposing)     {         if (!disposedValue)         {             if (disposing)             {                 // TODO: 釋放托管狀態(tài)(托管對象)             }              // TODO: 釋放未托管的資源(未托管的對象)并替代終結(jié)器             // TODO: 將大型字段設(shè)置為 null             disposedValue = true;         }     }      ~MyClass()     {         Dispose(disposing: false);     }      public void Dispose()     {         Dispose(disposing: true);         GC.SuppressFinalize(this);     } }

如果你看到了這兒,建議把上面這個套路模板存下來。這算是最完整的一個版本,網(wǎng)上能找到的,大多是簡化版。

其實,我們經(jīng)常使用的很多類,都實現(xiàn)了IDisposable接口。比如說,凡是可以用using來進(jìn)行調(diào)用類,就都實現(xiàn)了IDisposable接口。另外有一些類,把Dispose改成了一個別的名字,比方IO里的Close方法,就是一個Dispose。

另外,如果對象實現(xiàn)了IDisposable接口,而我們直接new了這個對象,那在使用結(jié)束后,我們就需要Dispose這個對象。因為既然設(shè)計者選擇了Dispose,那結(jié)束時調(diào)用Dispose就是正確的。

三、總結(jié)

最后做個簡單的總結(jié)。

垃圾回收模式選擇:應(yīng)用程序可分配的資源少,或者能夠競爭到的資源少,就使用工作站模式,反之就使用服務(wù)器模式。

在回收處理上,托管資源就扔給GC自動處理,非托管資源需要手動處理:

其中:

Finalize是標(biāo)記出非托管資源可被回收,然后由GC去執(zhí)行回收工作

Dispose是直接調(diào)用,并即時回收。

“如何理解Dotnet的垃圾回收”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI