溫故之.NET記憶體管理

JameLee發表於2018-11-19

.NET 記憶體管理是自動進行的,包括以下幾個過程

  • 記憶體分配
  • 記憶體釋放
  • 代(Generations
  • 非託管資源的記憶體釋放

記憶體分配

當初始化一個程式時,執行時會為該程式分配一個連續的地址空間區域——即為託管堆。

託管堆就像一個管家一樣,始終持有一把鑰匙的鑰匙(一個指標)——下一個空房間(可用空間首地址,即為下一個物件分配的空間的開始位置)。當然,最開始管家手中的鑰匙,是大門的鑰匙(即託管堆的基地址)。

所有的引用型別,都是在託管堆上分配的;而值型別的空間則在棧上分配。

需要注意的是:

  1. 引用型別在託管堆上分配空間(房間)之後,對這個空間的引用(鑰匙)則是放在棧上(管家手中)的
  2. 對引用(鑰匙)的複製,屬於簡單的複製(淺拷貝)。因為這也僅僅是多了一把鑰匙而已,這兩把鑰匙都只能開同一個房間(兩個引用都指向同一個地址空間)

在託管堆中進行記憶體的分配,還可以帶來效能的優勢:

  • 執行時為新物件分配記憶體時,通過不斷的更改指標的值(更改管家手中的鑰匙),而不是從系統記憶體中新分配記憶體(即建造一個新房間)。這樣,記憶體分配的速度幾乎可以同在棧上分配一樣快了
  • 記憶體分配的連續性,可以做到快速的訪問這些物件

不過,這裡有個前提

託管堆中的記憶體足夠應用程式使用。如果不夠,執行時將會頻繁的進行GC,這會對效能造成很大的影響。比如在Unity3D的開發中,頻繁的GC可能會造成遊戲畫面不連續。

記憶體的釋放

GC會根據物件的分配,來決定該物件回收的最佳時機。

它通過檢查應用程式的根來確定不再使用的物件,每個應用程式都有一組根(包含執行緒堆疊和CPU暫存器上的靜態欄位、區域性變數和引數),每個根要麼引用託管堆中的物件,要麼被設定為null

GC通過訪問JIT和執行時維護的活動根的列表來檢查應用程式的根(可訪問性檢查),同時建立一個可訪問的物件圖。不在該圖中的物件,即表示不可達(無法從根訪問),將被GC視為垃圾,並釋放為這些物件分配的記憶體。

在回收過程中,如果發現大量不可訪問的物件,則會使用記憶體複製功能來壓縮記憶體中可訪問的物件:移動物件,以保證可訪問的物件在一塊連續的記憶體空間內。
同時,對託管堆指標進行更正(重新為管家拿一把鑰匙)。這樣的好處是,可以讓剩下的記憶體空間連續。

值得注意的是,為了效能,在進行記憶體複製的時候,將不會處理託管堆中的大型物件(如影像)。
其一,這些大物件的移動可能會花很長時間;
其二,GC的過程中,會掛起正在執行的執行緒,如果在這過程中去移動這些大物件,則可能會造成程式假死。

代(Generations

為了優化GC效能,託管堆被分為了三代:第0代、第1代和第2代。

其垃圾回收演算法的原理如下:

  • 壓縮一部分記憶體要比壓縮整個託管堆快
  • 較新的物件的生存期較短,較老的物件的生存期較長
  • 較新的物件與其他物件有更大的關聯性,且基本上會在某一時間段內被應用程式訪問

鑑於以上原理,有以下的過程:

  1. 新的物件儲存在第0
  2. 老的物件如果未被回收,則升級為第1代和第2
  3. GC在第0代已滿的時候,將回收第0代中的物件(新物件),而這往往可以回收足夠多的記憶體
  4. 在第0代回收的過程中,未被回收的物件,將會升級為第1
  5. 若第0代的回收中,未能回收到足夠的記憶體,這時,GC將對第1代的物件進行回收
  6. 以此類推,第1代未被回收的,將會升級為第2代,等等。

非託管資源的記憶體釋放

非託管資源與託管資源不同,它們的記憶體需要我們顯式的釋放。

其釋放方式,除了上一篇文章溫故之.NET託管資源與非託管資源中介紹的方式外,還有另外一種方式。
這種方式我們將在下一篇文章【溫故之.NET垃圾回收】中一一道來。

小結

每次GC時,做了什麼

  • 檢索堆上的每個物件
  • 搜尋所有當前物件引用以確定堆上的物件是否仍在作用域內
  • 不在作用域內的物件被標記為刪除
  • 刪除被標記的物件並將記憶體返回給堆

故堆上的物件越多,程式碼中的引用數越多,GC就越費時。
另外需要注意的是,每次GC時,其都會掛起當前正在執行的執行緒,這肯定會對效能有影響,因此需要避免過多的GC

何時觸發GC

  • 堆分配時堆上的可用記憶體不足時觸發GC:順序為第0代、第1代、第2代,具體可見上面【代(Generations)】
  • GC會不時的自動執行(頻率因平臺而異),此即為“合適的時機”,但第一條中的情況下,一定會執行
  • 手動GC:呼叫 GC.Collect()GC.Collect(int generation)
  • 作業系統記憶體不足時,會觸發GC

至此,本節內容講解完畢。歡迎關注公眾號【嘿嘿的學習日記】,所有的文章,都會在公眾號首發,Thank you~

公眾號二維碼

相關文章