亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C# > 正文

詳談.net中的垃圾回收機制

2020-01-24 03:27:52
字體:
來源:轉載
供稿:網友

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

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

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

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不會是1,因為Finalizer不會馬上被觸發,要等到有一次回收操作后才會被觸發。 
  Console.WriteLine(CountObject.Count); 
  Console.ReadLine(); 
}

  注意以上代碼要是改用C++寫的話會發生內存泄漏,因為我們沒有用delete操作符手動清理內存,但是在托管代碼中卻不會發生內存泄漏,因為GC會自動檢測沒有引用了的對象并回收。這里作者推薦你只在實現IDisposable接口時配合使用Finalizer,在其他的情況下不要使用(可能會有特殊情況)。
3. 對象的復活
  什么?回收的對象也可以"復活"嗎?沒錯,雖然這么說的定義不準確。讓我們先來看一段代碼:
復制代碼 代碼如下:

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(); 
    // 看到了嗎,在這里“復活”了。 
    Console.WriteLine(Instance.Data); 
    Instance = null; 
    GC.Collect(); 
    Console.ReadLine(); 
  } 
}

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

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); 
  } 
}

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

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); 
    // 從這里開始,Fat對象可以被回收了。 
    oFat = null; 
    if (oFatRef.IsAlive) { 
      Console.WriteLine(((Fat) oFatRef.Target).Data); // 1 
    } 
    // 強制回收。 
    GC.Collect(); 
    Console.WriteLine(oFatRef.IsAlive); // False 
    Console.ReadLine(); 
  } 
}

  這里我們的Fat其實并不是很胖,但是可以體現示例的本意:如何使用弱引用。那如果Fat有Finalizer呢,會怎樣?如果Fat有Finalizer那么我們可能會用到WeakReference的另一個構造函數,當中有一參數叫做TrackResurrection,如果是True,只要Fat的內存沒被釋放我們就可以用它,也就是說Fat的Finalizer執行后我們還是可以恢復Fat(相當于第一次回收操作后還可恢復Fat);如果TrackResurrection是False,那么第一次回收操作后就不能恢復Fat對象了。
5. 總結
  我在這里寫出了正篇文章的要點:
一個對象只當在沒有任何引用的情況下才會被回收。
一個對象的內存不是馬上釋放的,GC會在任何時候將其回收。一般情況下不要強制回收工作。
如果沒有特殊的需要不要寫Finalizer。
不要在Finalizer中寫一些有時間邏輯的代碼。
在任何有非托管資源或含有Dispose的成員的類中實現IDisposable接口。
按照給出的Dispose設計寫自己的Dispose代碼。
當用胖對象時可以考慮弱引用的使用。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
精品久久久国产精品999| 色久欧美在线视频观看| 亚洲国产精品va在看黑人| 91精品国产高清久久久久久| 国产欧美在线看| 亚洲电影在线观看| 国内揄拍国内精品少妇国语| 中文字幕日韩免费视频| 久久视频在线视频| 精品国内产的精品视频在线观看| 性日韩欧美在线视频| 国产精品白嫩初高中害羞小美女| 成人免费激情视频| 国产成人黄色av| 日韩欧美第一页| 狠狠色狠狠色综合日日五| 亚洲性视频网站| 午夜精品一区二区三区在线播放| 欧美日韩亚洲视频| 久久精品国产综合| 欧美日本中文字幕| 国产色婷婷国产综合在线理论片a| 国产精品麻豆va在线播放| 96国产粉嫩美女| 精品美女国产在线| 亚洲片在线观看| 青草青草久热精品视频在线观看| 欧美日韩xxxxx| 久久亚洲春色中文字幕| 欧美一级免费视频| 日韩视频免费中文字幕| 永久免费精品影视网站| 欧美性受xxxx白人性爽| 国产一区二区久久精品| 亚洲成人网久久久| 一区二区三区视频免费| 国产自产女人91一区在线观看| 91精品视频专区| 51视频国产精品一区二区| 欧美日韩中文字幕综合视频| 日本中文字幕成人| 这里只有精品丝袜| 午夜精品久久久久久久久久久久久| 亚洲欧美在线免费| 欧美精品久久久久久久免费观看| 国产99久久精品一区二区永久免费| 日韩在线一区二区三区免费视频| 北条麻妃一区二区在线观看| 亚洲色图日韩av| 日韩免费高清在线观看| 欧美日韩精品在线观看| 久热精品视频在线观看一区| 国产精品久久久久久久一区探花| 日韩av电影中文字幕| 亚洲精品国产综合区久久久久久久| 欧美成人一区二区三区电影| 911国产网站尤物在线观看| 日韩电影中文 亚洲精品乱码| 欧美日韩不卡合集视频| 国内精品久久久久久久| 亚洲电影av在线| 久久网福利资源网站| 成人性生交xxxxx网站| 福利视频导航一区| 欧美最顶级的aⅴ艳星| 国产一区视频在线| 亚洲欧美另类中文字幕| 久久免费视频网站| 日本一本a高清免费不卡| 亚洲片在线资源| 中文字幕亚洲无线码a| 国产成人精品av| 亚洲一区二区三区久久| 久久亚洲精品小早川怜子66| 国产精品欧美风情| 久久影院资源网| 成人黄色在线免费| 欧美在线影院在线视频| 精品久久久久久久久久| 亚洲精品动漫100p| 亚洲男人第一网站| 亚洲欧美另类在线观看| 国产精品久久久久久久久久久久久久| 亚洲色图av在线| 91免费精品视频| 日韩av不卡在线| 欧美精品免费看| 最近2019免费中文字幕视频三| 狠狠做深爱婷婷久久综合一区| 国产精品美女久久久久久免费| 欧美日韩亚洲网| 欧美精品成人在线| 97久久精品人人澡人人爽缅北| 91欧美日韩一区| 亚洲一区二区免费在线| 欧美综合国产精品久久丁香| 91色精品视频在线| 亚洲国产91色在线| 日韩国产激情在线| 91免费国产网站| 国产精品久久久一区| 亚洲一区二区在线| 亚洲欧美日韩精品久久亚洲区| 亚洲欧洲在线免费| 国产精品日韩在线一区| 国产极品精品在线观看| 国产综合香蕉五月婷在线| 久久久久久国产精品久久| 国产一区二区三区久久精品| 久久资源免费视频| 欧美大尺度电影在线观看| 黑人巨大精品欧美一区二区三区| 国产一区二区三区久久精品| 久久99久久久久久久噜噜| 热re91久久精品国99热蜜臀| 国产精品ⅴa在线观看h| 欧美精品手机在线| 久久久久中文字幕2018| 成人黄色片在线| 日韩欧美aⅴ综合网站发布| 欧美视频免费在线观看| 精品伊人久久97| 日韩国产欧美精品在线| 欧美自拍视频在线观看| 欧美精品制服第一页| www.日本久久久久com.| 亚洲天堂网站在线观看视频| 久久综合伊人77777蜜臀| 欧美电影电视剧在线观看| 77777少妇光屁股久久一区| 成人免费福利在线| 97精品伊人久久久大香线蕉| 成人黄色av播放免费| 国产91对白在线播放| 亚洲视频欧美视频| 高清一区二区三区日本久| 国产精品一区二区久久国产| 欧美亚洲日本网站| 久久艹在线视频| 亚洲网址你懂得| 欧美成人免费网| 曰本色欧美视频在线| 日产精品久久久一区二区福利| 国产精品免费观看在线| 91精品啪在线观看麻豆免费| 国产精品一区二区三区免费视频| 91欧美精品午夜性色福利在线| 日韩欧美亚洲综合| 亚洲天堂精品在线| 久久精品国产久精国产思思| 久久露脸国产精品| 国产精品视频专区| 国产美女被下药99| 国产一区二区三区视频| 日韩美女在线观看一区| 精品国产一区二区三区久久| 亚洲高清不卡av| 亚洲va国产va天堂va久久| 久久影视电视剧凤归四时歌| 亚洲男女性事视频| 国产精品国产三级国产专播精品人| 国产丝袜一区二区三区| 欧美激情久久久| 中文字幕日韩av电影|