欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳談.net中的垃圾回收機(jī)制

 更新時(shí)間:2013年04月16日 17:03:54   作者:  
詳談.net中的垃圾回收機(jī)制,需要的朋友可以參考一下

1. 自動(dòng)內(nèi)存管理和GC
  在原始程序中堆的內(nèi)存分配是這樣的:找到第一個(gè)有足夠空間的內(nèi)存地址(沒被占用的),然后將該內(nèi)存分配。當(dāng)程序不再需要此內(nèi)存中的信息時(shí)程序員需要手動(dòng)將此內(nèi)存釋放。堆的內(nèi)存是公用的,也就是說所有進(jìn)程都有可能覆蓋另一進(jìn)程的內(nèi)存內(nèi)容,這就是為什么很多設(shè)計(jì)不當(dāng)?shù)某绦蛏踔習(xí)尣僮飨到y(tǒng)本身都down掉。我們有時(shí)碰到的程序莫名其妙的死掉了(隨機(jī)現(xiàn)象),也是因?yàn)閮?nèi)存管理不當(dāng)引起的(可能由于本身程序的內(nèi)存問題或是外來程序造成的)。另一個(gè)常見的實(shí)例就是大家經(jīng)常看到的游戲的Trainer,他們通過直接修改游戲的內(nèi)存達(dá)到"無敵"的效果。明白了這些我們可以想象如果內(nèi)存地址被用混亂了的話會(huì)多么危險(xiǎn),我們也可以想象為什么C++程序員(某些)一提起指針就頭疼的原因了。另外,如果程序中的內(nèi)存不被程序員手動(dòng)釋放的話那么這個(gè)內(nèi)存就不會(huì)被重新分配,直到電腦重起為止,也就是我們所說的內(nèi)存泄漏。所說的這些是在非托管代碼中,CLR通過AppDomain實(shí)現(xiàn)代碼間的隔離避免了這些內(nèi)存管理問題,也就是說一個(gè)AppDomain在一般情況下不能讀/寫另一AppDomain的內(nèi)存。托管內(nèi)存釋放就由GC(Garbage Collector)來負(fù)責(zé)。我們要進(jìn)一步講述的就是這個(gè)GC,但是在這之前要先講一下托管代碼中內(nèi)存的分配,托管堆中內(nèi)存的分配是順序的,也就是說一個(gè)挨著一個(gè)的分配。這樣內(nèi)存分配的速度就要比原始程序高,但是高出的速度會(huì)被GC找回去。為什么?看過GC的工作方式后你就會(huì)知道答案了。
2. GC工作方式
  首先我們要知道托管代碼中的對(duì)象什么時(shí)候回收我們管不了(除非用GC.Collect**GC回收,這不推薦,后面會(huì)說明為什么)。GC會(huì)在它"高興"的時(shí)候執(zhí)行一次回收(這有許多原因,比如內(nèi)存不夠用時(shí)。這樣做是為了提高內(nèi)存分配、回收的效率)。那么如果我們用Destructor呢?同樣不行,因?yàn)?NET中Destructor的概念已經(jīng)不存在了,它變成了Finalizer,這會(huì)在后面講到。目前請(qǐng)記住一個(gè)對(duì)象只有在沒有任何引用的情況下才能夠被回收。為了說明這一點(diǎn)請(qǐng)看下面這一段代碼:
復(fù)制代碼 代碼如下:

view sourceprint?object objA = new object(); 
object objB = objA; 
objA = null; 
// **回收。 
GC.Collect(); 
objB.ToString();

  這里objA引用的對(duì)象并沒有被回收,因?yàn)檫@個(gè)對(duì)象還有另一個(gè)引用,ObjB。對(duì)象在沒有任何引用后就有條件被回收了。
  當(dāng)GC回收時(shí),它會(huì)做以下幾步:
  1、確定對(duì)象沒有任何引用。
  2、檢查對(duì)象是否在Finalizer表上有記錄。如果在Finalizer表上有記錄,那么將記錄移到另外的一張表上,在這里我們叫它Finalizer2。如果不在Finalizer2表上有記錄,那么釋放內(nèi)存。在Finalizer2表上的對(duì)象的Finalizer會(huì)在另外一個(gè)low priority的線程上執(zhí)行后從表上刪除。當(dāng)對(duì)象被創(chuàng)建時(shí)GC會(huì)檢查對(duì)象是否有Finalizer,如果有就會(huì)在Finalizer表中添加紀(jì)錄。我們這里所說的記錄其實(shí)就是指針。如果仔細(xì)看這幾個(gè)步驟,我們就會(huì)發(fā)現(xiàn)有Finalizer的對(duì)象第一次不會(huì)被回收,也就是,有Finalizer的對(duì)象要一次以上的Collect操作才會(huì)被回收,這樣就要慢一步,所以作者推薦除非是絕對(duì)需要不要?jiǎng)?chuàng)建Finalizer。
  GC為了提高回收的效率使用了Generation的概念,原理是這樣的,第一次回收之前創(chuàng)建的對(duì)象屬于Generation 0,之后,每次回收時(shí)這個(gè)Generation的號(hào)碼就會(huì)向后挪一,也就是說,第二次回收時(shí)原來的Generation 0變成了Generation 1,而在第一次回收后和第二次回收前創(chuàng)建的對(duì)象將屬于Generation 0。GC會(huì)先試著在屬于Generation 0的對(duì)象中回收,因?yàn)檫@些是最新的,所以最有可能會(huì)被回收,比如一些函數(shù)中的局部變量在退出函數(shù)時(shí)就沒有引用了(可被回收)。如果在Generation 0中回收了足夠的內(nèi)存,那么GC就不會(huì)再接著回收了,如果回收的還不夠,那么GC就試著在Generation 1中回收,如果還不夠就在Generation 2中回收,以此類推。Generation也有個(gè)最大限制,根據(jù)Framework版本而定,可以用GC.MaxGeneration獲得。在回收了內(nèi)存之后GC會(huì)重新排整內(nèi)存,讓數(shù)據(jù)間沒有空格,這樣是因?yàn)镃LR順序分配內(nèi)存,所以內(nèi)存之間不能有空著的內(nèi)存?,F(xiàn)在我們知道每次回收時(shí)都會(huì)浪費(fèi)一定的CPU時(shí)間,這就是我說的一般不要手動(dòng)GC.Collect的原因。
  當(dāng)我們用Destructor的語(yǔ)法時(shí),編譯器會(huì)自動(dòng)將它寫為protected virtual void Finalize(),這個(gè)方法就是我所說的Finalizer。就象它的名字所說,它用來結(jié)束某些事物,不是用來摧毀(Destruct)事物。在Visual Basic中它就是以Finalize方法的形式出現(xiàn)的,所以Visual Basic程序員就不用操心了。C#程序員得用Destructor的語(yǔ)法寫Finalizer,不過千萬不要弄混了,.NET中已經(jīng)沒有Destructor了。C++中我們可以準(zhǔn)確的知道什么時(shí)候會(huì)執(zhí)行Destructor,不過在.NET中我們不能知道什么時(shí)候會(huì)執(zhí)行Finalizer,因?yàn)樗窃诘谝淮螌?duì)象回收操作后才執(zhí)行的。我們也不能知道Finalizer的執(zhí)行順序,也就是說同樣的情況下,A的Finalize可能先被執(zhí)行,B的后執(zhí)行,也可能A的后執(zhí)行而B的先執(zhí)行。也就是說,在Finalizer中我們的代碼不能有任何的時(shí)間邏輯。下面我們以計(jì)算一個(gè)類有多少個(gè)實(shí)例為示例,指出Finalizer與Destructor的不同并指出在Finalizer中有時(shí)間邏輯的錯(cuò)誤:
復(fù)制代碼 代碼如下:

view sourceprint?public class CountObject { 
  public static int Count = 0; 
  public CountObject() { 
    Count++; 
  } 
  ~CountObject() { 
    Count--; 
  } 

static void Main() { 
  CountObject obj; 
  for (int i = 0; i < 5; i++) { 
    obj = null; // 這一步多余,這么寫只是為了更清晰些! 
   obj = new CountObject(); 
  } 
  // Count不會(huì)是1,因?yàn)镕inalizer不會(huì)馬上被觸發(fā),要等到有一次回收操作后才會(huì)被觸發(fā)。 
  Console.WriteLine(CountObject.Count); 
  Console.ReadLine(); 
}

  注意以上代碼要是改用C++寫的話會(huì)發(fā)生內(nèi)存泄漏,因?yàn)槲覀儧]有用delete操作符手動(dòng)清理內(nèi)存,但是在托管代碼中卻不會(huì)發(fā)生內(nèi)存泄漏,因?yàn)镚C會(huì)自動(dòng)檢測(cè)沒有引用了的對(duì)象并回收。這里作者推薦你只在實(shí)現(xiàn)IDisposable接口時(shí)配合使用Finalizer,在其他的情況下不要使用(可能會(huì)有特殊情況)。
3. 對(duì)象的復(fù)活
  什么?回收的對(duì)象也可以"復(fù)活"嗎?沒錯(cuò),雖然這么說的定義不準(zhǔn)確。讓我們先來看一段代碼:
復(fù)制代碼 代碼如下:

view sourceprint?public class Resurrection { 
  public int Data; 
  public Resurrection(int data) { 
    this.Data = data; 
  } 
  ~Resurrection() { 
    Main.Instance = this; 
  } 

public class Main { 
  public static Resurrection Instance; 
  public static void Main() { 
    Instance = new Resurrection(1); 
    Instance = null; 
    GC.Collect(); 
    GC.WaitForPendingFinalizers(); 
    // 看到了嗎,在這里“復(fù)活”了。 
    Console.WriteLine(Instance.Data); 
    Instance = null; 
    GC.Collect(); 
    Console.ReadLine(); 
  } 
}

  你可能會(huì)問:"既然這個(gè)對(duì)象能復(fù)活,那么這個(gè)對(duì)象在程序結(jié)束后會(huì)被回收嗎?"。會(huì),"為什么?"。讓我們按照GC的工作方式走一遍你就明白是怎么回事了。
  1、執(zhí)行Collect。檢查引用。沒問題,對(duì)象已經(jīng)沒有引用了。
  2、創(chuàng)建新實(shí)例時(shí)已經(jīng)在Finalizer表上作了紀(jì)錄,所以我們檢查到了對(duì)象有Finalizer。
  3、因?yàn)椴榈搅薋inalizer,所以將記錄移到Finalizer2表上。
  4、在Finalizer2表上有記錄,所以不釋放內(nèi)存。
  5、Collect執(zhí)行完畢。這時(shí)我們用了GC.WaitForPendingFinalizers,所以我們將等待所有Finalizer2表上的Finalizers的執(zhí)行。
  6、Finalizer執(zhí)行后我們的Instance就又引用了我們的對(duì)象。(復(fù)活了)
  7、再一次去除所有的引用。
  8、執(zhí)行Collect。檢查引用。沒問題。
  9、由于上次已經(jīng)將記錄從Finalizer表刪除,所以這次沒有查到對(duì)象有Finalizer。
  10、在Finalizer2表上也不存在,所以對(duì)象的內(nèi)存被釋放了。
  非托管資源的釋放到現(xiàn)在為止,我們說了托管內(nèi)存的管理,那么當(dāng)我們利用如數(shù)據(jù)庫(kù)、文件等非托管資源時(shí)呢?這時(shí)我們就要用到.NET Framework中的標(biāo)準(zhǔn):IDisposable接口。按照標(biāo)準(zhǔn),所有有需要手動(dòng)釋放非托管資源的類都得實(shí)現(xiàn)此接口。這個(gè)接口只有一個(gè)方法,Dispose(),不過有相對(duì)的Guidelines指示如何實(shí)現(xiàn)此接口,在這里我向大家說一說。實(shí)現(xiàn)IDisposable這個(gè)接口的類需要有這樣的結(jié)構(gòu):
復(fù)制代碼 代碼如下:

view sourceprint?public class Base : IDisposable { 
  public void Dispose() { 
    this.Dispose(true); 
    GC.SupressFinalize(this); 
  } 
  protected virtual void Dispose(bool disposing) { 
    if (disposing) { 
      // 托管類 
    } 
    // 非托管資源釋放 
  } 
  ~Base() { 
    this.Dispose(false); 
  } 

public class Derive : Base { 
  protected override void Dispose(bool disposing) { 
    if (disposing) { 
      // 托管類 
    } 
    // 非托管資源釋放 
    base.Dispose(disposing); 
  } 
}

  為什么要這樣設(shè)計(jì)呢?讓我在后面解說一下?,F(xiàn)在我們講講實(shí)現(xiàn)這個(gè)Dispose方法的幾個(gè)準(zhǔn)則:它不能扔出任何錯(cuò)誤,重復(fù)的調(diào)用也不能扔出錯(cuò)誤。也就是說,如果我已經(jīng)調(diào)用了一個(gè)對(duì)象的Dispose,當(dāng)我第二次調(diào)用Dispose的時(shí)候程序不應(yīng)該出錯(cuò),簡(jiǎn)單地說程序在第二次調(diào)用Dispose時(shí)不會(huì)做任何事。這些可以通過一個(gè)flag或多重if判斷實(shí)現(xiàn)。一個(gè)對(duì)象的Dispose要做到釋放這個(gè)對(duì)象的所有資源。拿一個(gè)繼承類為例,繼承類中用到了非托管資源所以它實(shí)現(xiàn)了IDisposable接口,如果繼承類的基類也用到了非托管資源那么基類也得被釋放,基類的資源如何在繼承類中釋放呢?當(dāng)然是通過一個(gè)virtual/Overridable方法了,這樣我們能保證每個(gè)Dispose都被調(diào)用到。這就是為什么我們的設(shè)計(jì)有一個(gè)virtual/Overridable的Dispose方法。注意我們首先要釋放繼承類的資源然后再釋放基類的資源。因?yàn)榉峭泄苜Y源一定要被保障正確釋放所以我們要定義一個(gè)Finalizer來避免程序員忘了調(diào)用Dispose的情況。上面的設(shè)計(jì)就采用了這種形式。如果我們手動(dòng)調(diào)用Dispose方法就沒有必要再保留Finalizer了,所以在Dispose中我們用了GC.SupressFinalize將對(duì)象從Finalizer表去掉,這樣再回收時(shí)速度會(huì)更快。那么那個(gè)disposing和"托管類"是怎么回事呢?是這樣:在"托管類"中寫所有你想在調(diào)用Dispose時(shí)讓其處于可釋放狀態(tài)的托管代碼。還記得我們說過我們不知道托管代碼是什么時(shí)候釋放的嗎?在這里我們只是去掉成員對(duì)象的引用讓它處于可被回收狀態(tài),并不是直接釋放內(nèi)存。在"托管類"中這里我們也要寫上所有實(shí)現(xiàn)了IDisposable的成員對(duì)象,因?yàn)樗麄円灿蠨ispose,所以也需要在對(duì)象的Dispose中調(diào)用他們的Dispose,這樣才能保證第二個(gè)準(zhǔn)則。disposing是為了區(qū)分Dispose的調(diào)用方法,如果我們手動(dòng)調(diào)用那么為了第二個(gè)準(zhǔn)則"托管類"部分當(dāng)然得執(zhí)行,但如果是Finalizer調(diào)用的Dispose,這時(shí)候?qū)ο笠呀?jīng)沒有任何引用,也就是說對(duì)象的成員自然也就不存在了(無引用),也就沒有必要執(zhí)行"托管類"部分了,因?yàn)樗麄円呀?jīng)處于可被回收狀態(tài)了。好了,這就是IDisposable接口的全部了?,F(xiàn)在讓我們來回想一下,以前我們可能認(rèn)為有了Dispose內(nèi)存就會(huì)馬上被釋放,這是錯(cuò)誤的。只有非托管內(nèi)存才會(huì)被馬上釋放,托管內(nèi)存的釋放由GC管理,我們不用管。
4. 弱引用的使用
   A = B,我們稱這樣的引用叫做強(qiáng)引用,GC就是通過檢查強(qiáng)引用來決定一個(gè)對(duì)象是否是可以回收的。另外還有一種引用稱作弱引用(WeakReference),這種引用不影響GC回收,這就是它的用處所在。你會(huì)問到底有什么用處?,F(xiàn)在我們來假設(shè)我們有一個(gè)很胖的對(duì)象,也就是說它占用很多內(nèi)存。我們用過了這個(gè)對(duì)象,打算將它的引用去掉好讓GC可以回收內(nèi)存,但是功夫不大我們又需要這個(gè)對(duì)象了,沒辦法,重新創(chuàng)建實(shí)例,怎么創(chuàng)建這么慢???有什么辦法解決這樣的問題?有,將對(duì)象留在內(nèi)存中不就快了嘛!不過我們不想這樣胖得對(duì)象總占著內(nèi)存,而我們也不想總是創(chuàng)建這樣胖的新實(shí)例,因?yàn)檫@樣很耗時(shí)。那怎么辦……?聰明的朋友一定已經(jīng)猜到了我要說解決方法是弱引用。不錯(cuò),就是它。我們可以創(chuàng)建一個(gè)這個(gè)胖對(duì)象的弱引用,這樣在內(nèi)存不夠時(shí)GC可以回收,不影響內(nèi)存使用,而在沒有被GC回收前我們還可以再次利用該對(duì)象。這里有一個(gè)示例:
復(fù)制代碼 代碼如下:

view sourceprint?public class Fat { 
  public int Data; 
  public Fat(int data) { 
    this.Data = data; 
  } 

public class Main { 
  public static void Main() { 
    Fat oFat = new Fat(1); 
    WeakReference oFatRef = new WeakReference(oFat); 
    // 從這里開始,F(xiàn)at對(duì)象可以被回收了。 
    oFat = null; 
    if (oFatRef.IsAlive) { 
      Console.WriteLine(((Fat) oFatRef.Target).Data); // 1 
    } 
    // 強(qiáng)制回收。 
    GC.Collect(); 
    Console.WriteLine(oFatRef.IsAlive); // False 
    Console.ReadLine(); 
  } 
}

  這里我們的Fat其實(shí)并不是很胖,但是可以體現(xiàn)示例的本意:如何使用弱引用。那如果Fat有Finalizer呢,會(huì)怎樣?如果Fat有Finalizer那么我們可能會(huì)用到WeakReference的另一個(gè)構(gòu)造函數(shù),當(dāng)中有一參數(shù)叫做TrackResurrection,如果是True,只要Fat的內(nèi)存沒被釋放我們就可以用它,也就是說Fat的Finalizer執(zhí)行后我們還是可以恢復(fù)Fat(相當(dāng)于第一次回收操作后還可恢復(fù)Fat);如果TrackResurrection是False,那么第一次回收操作后就不能恢復(fù)Fat對(duì)象了。
5. 總結(jié)
  我在這里寫出了正篇文章的要點(diǎn):
一個(gè)對(duì)象只當(dāng)在沒有任何引用的情況下才會(huì)被回收。
一個(gè)對(duì)象的內(nèi)存不是馬上釋放的,GC會(huì)在任何時(shí)候?qū)⑵浠厥?。一般情況下不要強(qiáng)制回收工作。
如果沒有特殊的需要不要寫Finalizer。
不要在Finalizer中寫一些有時(shí)間邏輯的代碼。
在任何有非托管資源或含有Dispose的成員的類中實(shí)現(xiàn)IDisposable接口。
按照給出的Dispose設(shè)計(jì)寫自己的Dispose代碼。
當(dāng)用胖對(duì)象時(shí)可以考慮弱引用的使用。

相關(guān)文章

  • C#使用ImitateLogin模擬登錄百度

    C#使用ImitateLogin模擬登錄百度

    這篇文章主要介紹了C#使用ImitateLogin模擬登錄百度 的相關(guān)資料,需要的朋友可以參考下
    2015-12-12
  • C#使用Task實(shí)現(xiàn)異步方法

    C#使用Task實(shí)現(xiàn)異步方法

    本文主要介紹了C#使用Task實(shí)現(xiàn)異步方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • C#實(shí)現(xiàn)Datatable排序的方法

    C#實(shí)現(xiàn)Datatable排序的方法

    這篇文章主要介紹了C#實(shí)現(xiàn)Datatable排序的方法,在進(jìn)行C#數(shù)據(jù)庫(kù)程序設(shè)計(jì)的時(shí)候有不錯(cuò)的借鑒價(jià)值,需要的朋友可以參考下
    2014-09-09
  • C#私有構(gòu)造函數(shù)使用示例

    C#私有構(gòu)造函數(shù)使用示例

    本文主要介紹了C#私有構(gòu)造函數(shù)使用方法,私有構(gòu)造函數(shù)是一種特殊的實(shí)例構(gòu)造函數(shù)。它通常用在只包含靜態(tài)成員的類中。如果類具有一個(gè)或多個(gè)私有構(gòu)造函數(shù)而沒有公共構(gòu)造函數(shù),則其他類(除嵌套類外)無法創(chuàng)建該類的實(shí)例
    2014-01-01
  • C#獲取Description特性的擴(kuò)展類詳解

    C#獲取Description特性的擴(kuò)展類詳解

    這篇文章主要和大家詳細(xì)介紹一下C#獲取Description特性的擴(kuò)展類,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)有一定的幫助,需要的可以參考一下
    2022-06-06
  • C# Guid長(zhǎng)度雪花簡(jiǎn)單生成器的示例代碼

    C# Guid長(zhǎng)度雪花簡(jiǎn)單生成器的示例代碼

    這篇文章主要介紹了C# Guid長(zhǎng)度雪花簡(jiǎn)單生成器的示例代碼,幫助大家更好的理解和使用c#,感興趣的朋友可以了解下
    2020-12-12
  • .Net Winform開發(fā)筆記(一)

    .Net Winform開發(fā)筆記(一)

    理解“Windows 窗體應(yīng)用程序”項(xiàng)目中Program.cs文件中的main方法與傳統(tǒng)C++Console控制臺(tái)程序中的main方法的區(qū)別等等,感興趣的朋友可以了解下
    2013-01-01
  • c#使用xamarin編寫撥打電話程序

    c#使用xamarin編寫撥打電話程序

    Xamarin是一個(gè)行動(dòng)App開發(fā)平臺(tái),提供跨平臺(tái)開發(fā)能力,開發(fā)人員透過Xamarin開發(fā)工具與程序語(yǔ)言,即可開發(fā)出iOS、Android 與Windows 等平臺(tái)的原生(Native) App 應(yīng)用程序,不須個(gè)別使用各平臺(tái)的開發(fā)工具與程序語(yǔ)言,
    2015-05-05
  • 給c#添加SetTimeout和SetInterval函數(shù)

    給c#添加SetTimeout和SetInterval函數(shù)

    Javascript中的SetTimeout和SetInterval函數(shù)很方便,把他們移植到c#中來。
    2008-03-03
  • C#使用XmlDocument或XDocument創(chuàng)建xml文件

    C#使用XmlDocument或XDocument創(chuàng)建xml文件

    這篇文章主要為大家詳細(xì)介紹了C#使用XmlDocument或XDocument創(chuàng)建xml文件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-10-10

最新評(píng)論