日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

.net 垃圾回收机制

發布時間:2023/12/4 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 .net 垃圾回收机制 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

盡管在.NET framework下我們并不需要擔心內存管理和垃圾回收(Garbage Collection),但是我們還是應該了解它們,以優化我們的應用程序。同時,還需要具備一些基礎的內存管理工作機制的知識,這樣能夠有助于解釋我們日常程序編寫中的變量的行為。在本文中我們將深入理解垃圾回收器,還有如何利用靜態類成員來使我們的應用程序更高效。

* 更小的步伐 == 更高效的分配

為了更好地理解為什么更小的足跡會更高效,這需要我們對.NET的內存分配和垃圾回收專研得更深一些。

* 圖解:

讓我們來仔細看看GC。如果我們需要負責"清除垃圾",那么我們需要擬定一個高效的方案。很顯然,我們需要決定哪些東西是垃圾而哪些不是。
為了決定哪些是需要保留的,我們首先假設所有的東西都不是垃圾(墻角里堆著的舊報紙,閣樓里貯藏的廢物,壁櫥里的所有東西,等等)。假設在我們的生活當中有兩位朋友:Joseph Ivan Thomas(JIT)和Cindy Lorraine Richmond(CLR)。Joe和Cindy知道它們在使用什么,而且給了我們一張列表說明了我們需要需要些什么。我們將初始列表稱之為"根"列表,因為我們將它用作起始點。我們需要保存一張主列表來記錄出我們家中的必備物品。任何能夠使必備物品正常工作或使用的東西也將被添加到列表中來(如果我們要看電視,那么就不能扔掉遙控器,所以遙控器將被添加到列表。如果我們要使用電腦,那么鍵盤和顯示器就得添加到列表)。

這就是GC如何保存我們的物品的,它從即時編譯器(JIT)和通用語言運行時(CLR)中獲得"根"對象引用的列表,然后遞歸地搜索出其他對象引用來建立一張我們需要保存的物品的圖表。

根包括:

* 全局/靜態指針。為了使我們的對象不被垃圾回收掉的一種方式是將它們的引用保存在靜態變量中。
* 棧上的指針。我們不想丟掉應用程序中需要執行的線程里的東西。
* CPU寄存器指針。托管堆中哪些被CPU寄存器直接指向的內存地址上的東西必須得保留。

在以上圖片中,托管堆中的對象1、3、5都被根所引用,其中1和5時直接被引用,而3時在遞歸查找時被發現的。像我們之前的假設一樣,對象1是我們的電視機,對象3是我們的遙控器。在所有對象被遞歸查找出來之后我們將進入下一步--壓縮。

* 壓縮

我們現在已經繪制出哪些是我們需要保留的對象,那么我們就能夠通過移動"保留對象"來對托管堆進行整理。

幸運的是,在我們的房間里沒有必要為了放入別的東西而去清理空間。因為對象2已經不再需要了,所以GC會將對象3移下來,同時修復它指向對象1的指針。

然后,GC將對象5也向下移,

現在所有的東西都被清理干凈了,我們只需要寫一張便簽貼到壓縮后的堆上,讓Claire(指CLR)知道在哪兒放入新的對象就行了。

理解GC的本質會讓我們明白對象的移動是非常費力的。可以看出,假如我們能夠減少需要移動的物品大小是非常有意義的,通過更少的拷貝動作能夠使我們提升整個GC的處理性能。

* 托管堆之外是怎樣的情景呢?

作為負責垃圾回收的人員,有一個容易出現入的問題是在打掃房間時如何處理車里的東西,當我們打掃衛生時,我們需要將所有物品清理干凈。那家里的臺燈和車里的電池怎么辦?

在一些情況下,GC需要執行代碼來清理非托管資源(如文件,數據庫連接,網絡連接等),一種可能的方式是通過finalizer來進行處理。

class Sample


在對象創建期間,所有帶有finalizer的對象都將被添加到一個finalizer隊列中。對象1、4、5都有finalizer,且都已在finalizer隊列當中。讓我們來看看當對象2和4在應用程序中不再被引用,且系統正準備進行垃圾回收時會發生些什么。

對象2會像通常情況下那樣被垃圾回收器回收,但是當我們處理對象4時,GC發現它存在于finalizer隊列中,那么GC就不會回收對象4的內存空間,而是將對象4的finalizer移到一個叫做"freachable"的特殊隊列中。

有一個專門的線程來執行freachable隊列中的項,對象4的finalizer一旦被該線程所處理,就將從freachable隊列中被移除,然后對象4就等待被回收。

因此對象4將存活至下一輪的垃圾回收。

由于在類中添加一個finalizer會增加GC的工作量,這種工作是十分昂貴的,而且會影響垃圾回收的性能和我們的程序。最好只在你確認需要finalizer時才使用它。

在清理非托管資源時有一種更好的方法:在顯式地關閉連接時,使用IDisposalbe接口來代替finalizer進行清理工作會更好些。

* IDisposable

實現IDisposable接口的類需要執行Dispose()方法來做清理工作(這個方法是IDisposable接口中唯一的簽名)。因此假如我們使用如下的帶有finalizer的ResourceUser類:

public class ResourceUser?

{

~ResourceUser() // THIS IS A FINALIZER

{

// DO CLEANUP HERE

????????? }

}

我們可以使用IDisposable來以更好的方式實現相同的功能:

public class ResourceUser : IDisposable

{

IDisposable Members

}

IDisposable被集成在了using塊當中。在using()方法中聲明的對象在using塊的結尾處將調用Dispose()方法,using塊之外該對象將不再被引用,因為它已經被認為是需要進行垃圾回收的對象了。

public static void DoSomething()

{

ResourceUser rec = new ResourceUser();

using (rec)

{

// DO SOMETHING?

} // DISPOSE CALLED HERE

// DON'T ACCESS rec HERE

}

我更喜歡將對象聲明放到using塊中,因為這樣可視化很強,而且rec對象在using塊的作用域之外將不再有效。這種模式的寫法更符合IDisposable接口的初衷,但這并不是必須的。

public static void DoSomething()

{

using (ResourceUser rec = new ResourceUser())

{

// DO SOMETHING



} // DISPOSE CALLED HERE

}

在類中使用using()塊來實現IDisposable接口,能夠使我們在清理垃圾對象時不需要寫額外的代碼來強制GC回收我們的對象。

* 靜態方法

靜態方法屬于一種類型,而不是對象的實例,它允許創建能夠被類所共享的方法,且能夠達到"減肥"的效果,因為只有靜態方法的指針(8 bytes)在內存當中移動。靜態方法實體僅在應用程序生命周期的早期被一次性加載,而不是在我們的類實例中生成。當然,方法越大那么將其作為靜態就越高效。假如我們的方法很小(小于8 bytes),那么將其作為靜態方法反而會影響性能,因為這時指針比它指向的方法所占的空間還大些。

接著來看看例子...

我們的類中有一個公共的方法SayHello():

class Dude

{

private string _Name = "Don";



public void SayHello()

{

??????????????????? Console.WriteLine(this._Name + " says Hello");

????????? }

}

在每一個Dude類實例中SayHello()方法都會占用內存空間。

一種更高效的方式是采用靜態方法,這樣我們只需要在內存中放置唯一的SayHello()方法,而不論存在多少個Dude類實例。因為靜態成員不是實例成員,我們不能使用this指針來進行方法的引用。

class Dude

{

private string _Name = "Don";



public static void SayHello(string pName)

{

??????????????????? Console.WriteLine(pName + " says Hello");

????????? }

}

請注意我們在傳遞變量時棧上發生了些什么(可以參看<第二部分>)。我們需要通過例子的看看是否需要使用靜態方法來提升性能。例如,一個靜態方法需要很多參數而且沒有什么復雜的邏輯,那么在使用靜態方法時我們可能會降低性能。

* 靜態變量:注意了!

對于靜態變量,有兩件事情我們需要注意。假如我們的類中有一個靜態方法用于返回一個唯一值,而下面的實現會造成bug:

class Counter

{

private static int s_Number = 0;

public static int GetNextNumber()

{

int newNumber = s_Number;

// DO SOME STUFF????????

??????????????????? s_Number = newNumber + 1;

return newNumber;

????????? }

}

假如有兩個線程同時調用GetNextNumber()方法,而且它們在s_Number的值增加前都為newNumber分配了相同的值,那么它們將返回同樣的結果!

我們需要顯示地為方法中的靜態變量鎖住讀/寫內存的操作,以保證同一時刻只有一個線程能夠執行它們。線程管理是一個非常大的主題,而且有很多途徑可以解決線程同步的問題。使用lock關鍵字能讓代碼塊在同一時刻僅能夠被一個線程訪問。一種好的習慣是,你應該盡量鎖較短的代碼,因為在程序執行lock代碼塊時所有線程都要進入等待隊列,這是非常低效的。

class Counter

{

private static int s_Number = 0;

public static int GetNextNumber()

{

lock (typeof(Counter))

{

int newNumber = s_Number;

// DO SOME STUFF

???????????????????????????? newNumber += 1;

???????????????????????????? s_Number = newNumber;

return newNumber;

??????????????????? }

????????? }

}

* 靜態變量:再次注意了!

靜態變量引用需要注意的另一件事情是:記住,被"root"引用的事物是不會被GC清理掉的。我遇到過的一個最煩人的例子:

class Olympics

{

public static Collection<Runner> TryoutRunners;

}


class Runner

{

private string _fileName;

private FileStream _fStream;

public void GetStats()

{

??????????????????? FileInfo fInfo = new FileInfo(_fileName);

??????????????????? _fStream = _fileName.OpenRead();

????????? }

}

由于Runner集合在Olympics類中是靜態的,不僅集合中的對象不會被GC釋放(它們都直接被根所引用),而且你可能注意到了,每次執行GetStats()方法時都會為那個文件開放一個文件流,因為它沒有被關閉所以也不會被GC釋放,這個代碼將會給系統造成很大的災難。假如我們有100000個運動員來參加奧林匹克,那么會由于太多不可回收的對象而難以釋放內存。天啦,多差勁的性能呀!

* Singleton

有一種方法可以保證一個類的實例在內存中始終保持唯一,我們可以采用Gof中的Singleton模式。(Gof:Gang of Four,一部非常具有代表性的設計模式書籍的作者別稱,歸納了23種常用的設計模式)

public class Earth

{

private static Earth _instance = new Earth();

private Earth() { }

public static Earth GetInstance() { return _instance; }

}

我們的Earth類有一個私有構造器,所以Earth類能夠執行它的構造器來創建一個Earth實例。我們有一個Earth類的靜態實例,還有一個靜態方法來獲得這個實例。這種特殊的實現是線程安全的,因為CLR保證了靜態變量的創建是線程安全的。這是我認為在C#中實現singleton模式最為明智的方式。

* .NET Framework 2.0中的靜態類

在.NET 2.0 Framework中我們有一種靜態類,此類中的所有成員都是靜態的。這中特性對于工具類是非常有用的,而且能夠節省內存空間,因為該類只存在于內存中的某個地方,不能在任何情況下被實例化。

* 總結一下...

總的來說,我們能夠提升GC表現的方式有:

1. 清理工作。不要讓資源一直打開!盡可能地保證關閉所有打開的連接,清除所有非托管的資源。當使用非托管對象時,初始化工作盡量完些,清理工作要盡量及時點。

2. 不要過度地引用。需要時才使用引用對象,記住,如果你的對象是活動著的,所有被它引用的對象都不會被垃圾回收。當我們想清理一些類所引用的事物,可以通過將這些引用設置為null來移除它們。我喜歡采用的一種方式是將未使用的引用指向一個輕量級的NullObject來避免產生null引用的異常。在GC進行垃圾回收時,更少的引用將減少映射處理的壓力。

3. 少使用finalizer。Finalizer在垃圾回收時是非常昂貴的資源,我們應該只在必要時使用。如果我們使用IDisposable來代替finalizer會更高效些,因為我們的對象能夠直接被GC回收而不是在第二次回收時進行。

4. 盡量保持對象和它們的子對象在一塊兒。GC在復制大塊內存數據來放到一起時是很容易的,而復制堆中的碎片是很費勁的,所以當我們聲明一個包含許多其他對象的對象時,我們應該在初始化時盡量讓他們在一塊兒。

5. 最后,使用靜態方法來保持對象的輕便也是可行的。

轉載于:https://www.cnblogs.com/as_as/archive/2010/07/13/1776405.html

總結

以上是生活随笔為你收集整理的.net 垃圾回收机制的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。