垃圾回收機制,是否是這樣理解?

 目錄算法

  • 前言
  • 分配內存和資源初始化
  • 清理本地資源
  • 回收內存 & 垃圾回收算法
  • 垃圾回收機制:代

 

前言:資源的生存週期數據庫

   一、new一個對象時,調用IL命令newobj,爲資源類型分配內存。數組

   二、初始化內存,構造函數初始化資源的狀態。函數

     三、程序中來回的調用、訪問資源。性能

       四、摧毀資源的狀態並進行清理。this

     五、釋放內存。垃圾回收執行這一步。線程

 

1、分配內存和資源初始化翻譯

第1與第2步—如何分配內存和資源初始化?指針

首先CLR規定全部的資源都從託管堆中分配。此託管堆維護對象資源,會爲咱們自動管理對象狀態。  對象

進程初始化時,CLR會預留一塊連續的地址空間即託管堆,可是沒有對應的物理存儲空間。此託管堆上維護者一個指針,它指向下個對象在託管堆中的分配位置。

new操做符會生成一個IL指令newobj,指令會指導CLR進行如下工做。

  A、計算類型以及基類的字段所須要的空間。

  B、加上對象的開銷所需的字節數(類型對象指針以及同步塊索引)。

  C、CLR檢查保留區域是否可以提供分配對象所需的字節數,若是有就提交存儲。對象會在指針NewObjPtr指向的位置放入,爲對象分配的字節數清零,並調用實例化構造器返回對象的地址。

    當前指針會加上對象佔據的字節數,成爲一個新址,下一個對象的存儲地址。

託管堆上分配對象的前提是空間內存足夠,那就引出了下一個機制—垃圾回收機制,來回收內存,釋放資源。

 

2、清理本地資源

第4步—怎樣摧毀本地資源的狀態並進行清理?

方式一:隱式終結—Finalize()

    定義:垃圾回收器會在回收內存以前執行Finzlize()(若是此類實現了Finalize方法),垃圾回收會自動調用此方法。垃圾回收器會因隱式執行。

     語法:是在類名前加~,例如:~方法名(){}—Finalize方法。

     觸發:第0代滿、顯示調用System.GC的Collect方法、內存不足、卸載AppDomain

     原理:一個實現了Finalize方法的對象new以後,會在垃圾回收器維護的一個終結列表中添加一個指針指向這個新分配的這個對象。在回收內存以前調用它的Finalize方法。

     不足:Finalize釋放的是託管資源,垃圾回收器會在進行垃圾回收的時候釋放託管資源,咱們不肯定下次的垃圾回收發生在什麼時候,咱們想本身控制垃圾回收並控制菲菲託管資源的釋放。

        因此下面就使用顯示摧毀資源狀態(前提是肯定再也不使用,確認須要關閉)。好比:數據庫鏈接、文件讀寫

 

方式二:顯示終結—Dispose()、Close()

咱們經過書上的例子更直接:

下面具體討論一下咱們常用的Close()以及Dispose()

首先咱們看到了熟悉的 Finalize()方法—>~SafeHandle() ,還有須要咱們討論的Dispose()方法和Close()方法。另外,還有一個帶有參數的Dispose(Boolean disposeing)虛方法。

這裏的資源釋放統一處理 Dispose(Boolean disposeing) 方法,若是參數爲true,會標記此對象資源顯示關閉,可是沒有終結,能夠正常訪問字段。若是是false,那就終結對象,回收內存。

由於繼承了IDispose接口,因此要實現一個無參的Dispose()方法,而咱們常用的Close()是由於出於習慣以爲有一個叫Close()的方法彷佛更親切。因此就添加了一個Close()方法。沒有其餘特殊用途。

當咱們調用Dispose()或者Close()方法時,對象自己的內存尚未釋放,仍然須要垃圾回收器來回收內存。

 

因此到目前爲止咱們能夠經過三種方式來摧毀資源:

一、顯示調用Dispose()

二、顯式調用Close()

三、等待垃圾回收時,垃圾回收器自動調用Finalize方法進行摧毀。

四、一種Dispose()和Close()方法的變相模式using(){}

 

3、垃圾回收

第5步—如何釋放內存?

垃圾回收器檢查託管堆中是否有應用程序再也不使用的對象。有,回收內存(若是回收後,內存仍然不夠,就拋出內存溢出異常)。

垃圾回收器怎樣判斷對象正在使用?

每一個應用程序都包含一組根,每一個根都是一個存儲盒子,裏面包含着引用對象指針(要麼引用一個對象,要麼爲null)。

例如類中定義的任何靜態字段會被認爲有一個根,方法中的任何參數和局部變量也會被認爲有一個根。只有引用類型的變量才被認爲是根。

固然這裏有個前提:只有引用類型才能認爲是根,值類型除外。

借用一下書上的例子:類

JIT在生成CPU代碼的同時還會生成方法在本地CPU指令中的一個字節偏移範圍的記錄項,這個記錄項也包含着根的一組內存地址和CPU寄存器。 

上面的類在第一次調用方法 WriteBytes 的時候,JIT會將IL代碼翻譯成CPU指令,以下(x86 CPU):

一、寄存器:

  ebx在偏移到00000003處開始爲寄存器的根,到循環結束 00000028處結束根。此類爲實例,因此會有一個this指針,經過','後面的ecx寄存器傳遞,並存儲到前面的寄存器ebx。

  一樣,esi在偏移到00000005處開始爲寄存器的根,直到00000028根結束。它經過寄存器edx傳遞bytes[],並將數組存入到寄存器esi。

  對於edi來講,它傳遞的是Int32類型,值類型不會有根。

  後面的ecx0000000f開始做爲根,到000001e處根結束。

 

二、垃圾回收:若是在0000017處發生垃圾回收

  首先肯定,00000017處發生的垃圾回收,沒有到達ebx(this指針)、esi(byte[])的根結束位置00000028 ,也沒有到達 ecx(m_textWriter)根結束位置0000001e。

  A、收集根:

  (1)這三個寄存器中引用指向的對象都是根,並且這些根中所引用的堆中的對象也不能回收。

  (2)其次垃圾回收器會檢查線程棧上行,檢查每一個方法的內部表來肯定全部調用方法的根。

  (3)最後垃圾回收器將遍歷全部類型對象,來獲取靜態字段中存儲的根集合。

  B、標記階段

  (1)垃圾回收器開始執行時,它會假設堆中的全部對象都是垃圾。它會假設線程棧和堆沒有引用關聯,沒有CPU寄存器引用堆中的對象。也沒有靜態字段引用堆中的對象。

  (2)接着進入標記階段,沿着線程棧上行檢查全部根,若是發現一個根引用了一個對象,就對這個對象進行標記(同步塊索引字段上開啓一個bit=1的標識)。

  收集根並標記完後,會有標記和未標記的對象。標記的就是程序能夠繼續訪問的,反之就是不可達的垃圾。就會對垃圾進行回收內存。

  C、壓縮階段

  (1)垃圾回收器會線性遍歷堆,遇到垃圾對象時,檢查一下連續內存塊,若是較小就忽略,較大就會將非垃圾對象移動到這裏。

  (2)可是非垃圾對象以前的地址和寄存器等都會失效,垃圾回收器也會從新訪問根,生成新的地址等等。

  這樣程序內存的碎片化就獲得大幅度的控制,固然這也是犧牲了些許的性能。

 

4、代

代:是垃圾回收器採用的一種機制,目的就是爲了提升程序性能。

根據代機制,咱們能夠作出如下假設:

  • 對象越新,回收可能性很是大。
  • 對象越老,回收可能性很是小。
  • 回收堆中的部分,速度快於回收整個堆。

 

咱們的託管堆初始化時不會包含任何對象,咱們初始化的對象會添加到託管堆上,這些對象咱們稱爲第0代。第0代的存儲上限爲256KB,第一代爲2M·····

此時咱們標記第0代對象在堆上的存儲上限爲256KB(只是假設一下),以下圖:

5個對象A、B、C、D、E。程序運行一會以後,C和E變得不可達,等待垃圾回收器來回收內存。

 

每當第0代滿時,也就是當前堆中的對象達到了上限256KB,這時垃圾回收器開始執行垃圾回收,

若是此時分配新的對象F時,出現A~E達到分配上限256KB,垃圾回收器就會壓縮D使得和以前可用內存連續起來。

C和E回收了內存。這樣A、B、D進入第一代,第0代空,以下圖:

 

運行一段時間,B、H、J也是不可達狀態,同時在新分配L時第0代達到了上限256KB,以下圖:

如今第0代滿,垃圾回收器運行,它會壓縮I、K的內存與G連續。同時回收H、J。

此時第一代這個雖然有不可達的B對象,可是第一代沒有達到上限2M,垃圾回收器就不會對B進行回收。

回收後:咱們看到沒有對B進行回收,那是由於第一代沒有達到上限2M,這一切爲了性能,由於第一代中出現垃圾的頻率通常遠遠低於第0代甚至沒有垃圾。

這樣垃圾回收器就寧願讓垃圾暫時呆在那裏,暫時不去回收。這樣節省了時間,增長了效率。

 

程序依然運行,就會有更多的對象分配到堆上,同時產生更多的的垃圾,以下圖:

此時當分配P時,第0代滿,執行垃圾回收,回收地0代P、R

此時垃圾回收器檢測到第一代也超過限額2M,就檢測第一代中的對象進行垃圾回收。知道這個時候第一代的垃圾纔有幸回收。

回收後:此時會將第一代剩下的升級到第二代中,原來在第0代存活的對象,也會升級到第一代中。

垃圾回收器只有三代。由於CLR會根據實際狀況進行自動調節。3代足夠。

 

PS:在CLR初始化時,會對第0代、第一代、第二代進行內存限額設定:256KB、2M、10M。限額越大執行垃圾回收的頻率越低。

只在第0代滿時進行垃圾回收,當第0代滿,此時第一代也滿,纔會對第一代進行垃圾回收。因此效率上是能夠保證的。

當CLR檢測到回收第0代對象後,幾乎沒有回收多少內存,此時就會調整上限到512KB。一樣若是回收的垃圾不少,那調整到128KB。以此類推,自動調劑。

這樣的調節也會應用於第一代、第二代。

相關文章
相關標籤/搜索