.Net平臺的GC垃圾回收

Sol·wang發表於2021-05-23

一、先了解下必備的知識前提

記憶體中的託管與非託管,可簡單理解為:

託管:可藉助GC從記憶體中釋放的資料物件(以下要描述的內容點)

非託管:必須手工藉助Dispose釋放資源(實現自IDisposable)的物件

 

記憶體中有棧和堆的概念區分,僅簡單說明:

棧:先進後出 的特點(這裡不再詳細闡述)

堆:存放資料物件例項的記憶體空間(以下要描述的內容點)

 

二、.Net GC的簡單描述

GC垃圾回收是對於記憶體堆的處理過程。

當一個應用程式程式建立時,會為此應用程式在實體記憶體堆中分配一塊虛擬的連續性記憶體空間,以供應用程式後續執行時存放產生的資料物件例項。

GC是一個獨立的程式,用來自動維護管理記憶體堆中的空間分配和釋放。它通過一個或多個執行緒進行垃圾回收,預設啟用後臺執行緒垃圾回收。(關於前臺執行緒與後臺執行緒,可參考其它

 

三、.Net平臺的GC垃圾回收,什麼時候會被觸發呢?

1、當被分配的堆中虛擬記憶體空間不夠用時,系統會自動 回收/壓縮/擴大 被分配的虛擬記憶體塊,以適應新產生的資料物件儲存。

2、當整個實體記憶體不夠用時,系統會自動 回收/壓縮 各個程式佔用的記憶體空間,以適應新產生的資料物件儲存。

3、當應用程式中手動觸發GC回收時,GC按照手動指定的方式進行垃圾回收。

 

四、從作用域上 去理解堆中的代

先這樣去理解吧

假設一個例項變數宣告時的作用域較大,那它就不會馬上被回收,因為作用域大的因素,有可能後續程式時常還會被用到。

假設一個例項變數宣告時的作用域較小,那它就有可能被優先回收,因為生存週期較短,過了作用域範圍,此變數不會再被使用。

假設一個靜態的或全域性的作用域變數,那它通常不會被回收,因為這樣的全域性宣告會在任意程式碼段長期被使用。

 

所以,為了更好的回收,堆中將各資料物件例項歸納為:0代、1代、2代

0代:臨時或最新建立的資料物件例項。最常被回收的物件例項。

1代:一段時間內再次使用的資料物件例項,生命週期較長的資料物件例項。較少被回收的物件例項。

2代:常住記憶體的物件例項,如:靜態型別,全域性作用域等的物件例項。通常為應用程式退出後回收。

 

五、堆中物件 在代之間的轉移:倖存者的提升

應用程式持續執行中,

新建立的物件首先被放在0代中,當執行一段時間後,有些變數超出了自己所在的作用域,不會再被使用,會被GC清理;

由於有些變數作用域大,當前還未超出自己所在的作用域,接下來可能還會被使用,所以GC不會清理;

0代中,有些資料物件例項會被GC清理,有些資料例項物件未被GC清理,那麼,未被GC清理的資料物件例項,我們稱它為倖存者

此時,0代中的倖存者會被轉移到1代中(想想上面提到1代存放的是哪類物件例項...)

那麼,以此類推,長期/處處被使用的物件例項,就會從1代中轉移到2代中

因此,2代中存放的通常為靜態或全域性作用域或長期被使用到的物件例項。

 

六、GC是如何去確定要清理的物件例項?

GC在堆中生成各物件間的結構圖,作為回收物件的依據,找出非活動的物件。

所有資料物件例項之間的關聯引用關係,都會生成一個完整的結構圖,一些不在結構圖中的 或超出所在作用域的 或不再被繼續使用的物件例項,被稱為非活動物件。被視為GC要清理的物件。

準確的說:

  • 堆疊根
  • 垃圾回收控制程式碼
  • 靜態資料

 

七、手動GC垃圾回收

在某些不常見的情況下,強制回收可提高應用程式的效能。在此,可使用 GC.Collect 方法強制執行垃圾回收,從而誘導垃圾回收

注意,是誘導,而不是即刻回收。

為了考慮到應用程式當前的穩定執行,執行GC.Collect並不一定馬上產生效果,這裡僅僅是一個觸發,會去收集將要回收的物件,回收動作會在未來某個合適的時間段進行。(當然,也可以強制阻塞式回收,這裡略過)

(思考一下:無用的例項=null,是否告知GC為可回收的物件?再GC.Collect()後的效果。)

 

關於 GC.Collect 方法的引數,會用到上面提到的概念及場景:

  • 對指定的代進行回收
  • 指定回收次數
  • 強制回收 或 擇機回收
  • 阻塞式回收 或 後臺執行緒回收
  • 壓縮 或 清理

 

(阻塞式回收方式:都先停一停,先讓我回收完)

當然,通常建議:0代,擇機,後臺回收(阻塞式風險太大,通常選擇擇機方式,具體自我考量)

 

八、記憶體堆中的弱引用

當應用程式正在執行使用的物件,GC是不可能回收的,那麼,就認為應用程式對該物件具有強引用。

強引用:應用程式正在使用的物件例項,不能被GC回收。

弱引用:應用程式暫時沒使用的物件例項,暫時可被GC定義為可回收的例項,在回收之前,也可被應用程式再次使用後變為強引用。

 

假設一個物件例項被GC清理後,後續又被再次用到的場景,就會重新建立物件例項,那如果這個物件例項又比較大,這樣的頻繁建立... ...

當然還有優化的空間,所以,弱引用優化了以上場景。

弱引用的優點:對於頻繁建立的大例項,弱型別可以做到一次建立多次使用,避免大物件例項多次建立的效能消耗。

(對於小物件使用弱型別,所帶來的對物件管理上的效能消耗,是否值得)

若要對某物件建立弱引用,使用要跟蹤的物件例項建立 WeakReference。 然後將 Target 屬性設定為該物件,將該物件的原始引用設定為 null。(參考官方文件

也就是說:我們可以自定義控制哪些物件例項要不要暫時不被GC垃圾回收

 

九、多應用共享記憶體時的垃圾回收

當多個應用程式在一臺主機同時執行時,對記憶體空間大小的分配,建議是靈活可變的,以達到各應用程式對記憶體利用的平衡及穩定性。

如果啟用 gcTrimCommitOnLowMemory 設定,垃圾回收器會計算系統記憶體負載,並在負載達到 90% 時進入修整模式。除非負載下降到不到 85%,否則會一直處於修整模式。

如果條件允許,垃圾回收器可以決定 gcTrimCommitOnLowMemory 設定對當前應用沒有幫助並忽略它。

如下啟用 gcTrimCommitOnLowMemory 設定

1 <?xml version="1.0" encoding="UTF-8"?>
2 <configuration>
3     <runtime>
4         <gcTrimCommitOnLowMemory enabled="true"/>  
5     </runtime>
6 </configuration>

 

相關文章