一文瞭解.Net的CLR、GC記憶體管理 微軟官方文檔對記憶體管理和CLR的概述 什麼是托管代碼? 托管代碼就是執行過程交由運行時管理的代碼。 在這種情況下,相關的運行時稱為公共語言運行時 (CLR),不管使用的是哪種實現(例如 Mono、.NET Framework 或 .NET Core/.NET ...
-
一文瞭解.Net的CLR、GC記憶體管理
微軟官方文檔對記憶體管理和CLR的概述-
什麼是托管代碼?
- 托管代碼就是執行過程交由運行時管理的代碼。 在這種情況下,相關的運行時稱為公共語言運行時 (CLR),不管使用的是哪種實現(例如 Mono、.NET Framework 或 .NET Core/.NET 5+)。 CLR 負責提取托管代碼、將其編譯成機器代碼,然後執行它。 除此之外,運行時還提供多個重要服務,例如自動記憶體管理、安全邊界、類型安全,等等。
- 托管代碼是使用可在 .NET 上運行的一種高級語言(例如 C#、Visual Basic、F# 等)編寫的。 使用相應的編譯器編譯以這些語言編寫的代碼時,無法獲得機器代碼, 而是獲得 中間語言 代碼,然後運行時會對其進行編譯並將其執行。
-
什麼是中間語言?
- 中間語言是編譯使用高級 .NET 語言編寫的代碼後獲得的結果。對使用其中一種語言編寫的代碼進行編譯後,即可獲得 IL 所生成的二進位代碼。
- 從高級代碼生成 IL 後,你很有可能想要運行它。 CLR 此時將接管工作,啟動 實時 (JIT) 編譯過程,或者將代碼從 IL 實時 編譯成可以真正在 CPU 上運行的機器代碼。 這樣,CLR 就能確切地知道代碼的作用,並可以有效地 管理 代碼。
- 中間語言有時也稱為公共中間語言 (CIL) 或 Microsoft 中間語言 (MSIL)。
-
自動記憶體管理
自動記憶體管理是公共語言運行時在托管執行過程中提供的服務之一。 公共語言運行時的垃圾回收器為應用程式管理記憶體的分配和釋放。-
分配記憶體
- 初始化新進程時,運行時會為進程保留一個連續的地址空間區域。但是這個記憶體其實是虛擬的連續記憶體的,在物理記憶體上記憶體在物理上不一定連續的。這個保留的地址空間被稱為托管堆。 托管堆維護著一個指針,用它指向將在堆中分配的下一個對象的地址。
-
釋放記憶體(GC如何表示對象需要回收、GC執行的流程?)
- 垃圾回收器的優化引擎根據所執行的分配決定執行回收的最佳時間。 垃圾回收器在執行回收時,會釋放應用程式不再使用的對象的記憶體。 它通過檢查應用程式的根來確定不再使用的對象,垃圾回收器需要選定一系列的起點根(root)以保證對象的遍歷,提供給垃圾回收器創建有向引用圖的根,每次回收都會從根觸發去遍歷對象圖中所有被引用的對象並標記,然後是清理和壓縮未被引用的對象。
- 為了改進性能,運行時為單獨堆中的大型對象分配記憶體。 垃圾回收器會自動釋放大型對象的記憶體。 但是,為了避免移動記憶體中的大型對象,不會壓縮此記憶體。
-
-
托管堆的級別和觸發回收的時機
為優化垃圾回收器的性能,將托管堆分為三代:第 0 代、第 1 代和第 2 代。運行時的垃圾回收器將新對象存儲在第 0 級中。 在應用程式生存期的早期創建的對象如果未被回收,則被升級並存儲在第 1 級和第 2 級中。 GC的回收機制是通過檢查掃描根引用和標記來進行確定回收的,這個根也稱為GC根。執行第 1 代 GC 時,將同時回收第 1 代和第 0 代。 執行第 2 代 GC 時,將回收整個堆。 因此,第 2 代 GC 還可稱為“完整 GC”。-
第 0 代執行回收的時機
垃圾回收器在第 0 級托管堆已滿時執行回收。 如果應用程式在第 0 級托管堆已滿時嘗試新建對象,垃圾回收器將會發現第 0 級托管堆中沒有可分配給該對象的剩餘地址空間。 垃圾回收器執行回收,嘗試為對象釋放第 0 級托管堆中的地址空間。 垃圾回收器從檢查第 0 級托管堆中的對象(而不是托管堆中的所有對象)開始執行回收。 -
第 1 代對象的創建和執行回收時機
垃圾回收器執行第 0 級托管堆的回收後,會壓縮可訪問對象的記憶體,垃圾回收器升級這些對象,並考慮第 1 代托管堆的這一部分對象。 因為未被回收的對象往往具有較長的生存期,所以將它們升級至更高的級別很有意義。 因此,垃圾回收器在每次執行第 0 代托管堆的回收時,不必重新檢查第 1 代和第 2 代托管堆中的對象。 -
第 2 代 對象的創建和執行回收時機
圾回收器執行第 1 代托管堆的回收後,會壓縮可訪問對象的記憶體,垃圾回收器升級這些對象,並考慮第 2 代托管堆的這一部分對象。第 2 代托管堆中未被回收的對象會繼續保留在第 2 代托管堆中,直到在將來的回收中確定它們無法訪問為止。大型對象堆上的對象(有時稱為 第 3 代)也在第 2 代中收集。 -
垃圾回收器的優化引擎會決定是否需要檢查較舊的級別中的對象
如果第 0 級托管堆的回收沒有回收足夠的記憶體,不能使應用程式成功完成創建新對象的嘗試,垃圾回收器就會先執行第 1 級托管堆的回收,然後再執行第 2 級托管堆的回收。 如果這樣仍不能回收足夠的記憶體,垃圾回收器將執行第 2、1 和 0 級托管堆的回收。 每次回收後,垃圾回收器都會壓縮第 0 級托管堆中的可訪問對象並將它們升級至第 1 級托管堆。 第 1 級托管堆中未被回收的對象將會升級至第 2 級托管堆。 由於垃圾回收器只支持三個級別,因此第 2 級托管堆中未被回收的對象會繼續保留在第 2 級托管堆中,直到在將來的回收中確定它們為無法訪問為止。 -
非托管資源的記憶體釋放
對於應用程式創建的大多數對象,可以依賴垃圾回收器自動執行必要的記憶體管理任務。 但是,非托管資源需要顯式清除。 最常用的非托管資源類型是包裝操作系統資源的對象,例如,文件句柄、視窗句柄或網路連接。 雖然垃圾回收器可以跟蹤封裝非托管資源的托管對象的生存期,但卻無法具體瞭解如何清理資源。 創建封裝非托管資源的對象時,建議在公共 Dispose 方法中提供必要的代碼以清理非托管資源。 通過提供 Dispose 方法,對象的用戶可以在使用完對象後顯式釋放其記憶體。
-
-
85kb的劃分是指淺層對象還是深層對象?
假如我們現在有一個對象Order對象如下定義,那麼我們的order是應該在第0代還是在大對象堆呢?
答案是在第0代,但是OrderItems是在大對象堆中的,所以85kb的只計算了淺層對象的大小,不計算對象的深層大小。public class Progarm{ public static void main(){ var order=new Order();// 0代 } } public class Order { public OrderItem OrderItems{get private set;}=[5000];//>85kb位元組 3代 } public class OrderItem{ public string ProductId{get; private set;} }
-
垃圾回收
-
基礎
-
優點
- 開發人員不必手動釋放記憶體。
- 有效分配托管堆上的對象。
- 回收不再使用的對象,清除它們的記憶體,並保留記憶體以用於將來分配。 托管對象會自動獲取乾凈的內容來開始,因此,它們的構造函數不必對每個數據欄位進行初始化。
- 通過確保對象不能使用另一個對象的內容來提供記憶體安全。
-
垃圾回收的條件
- 系統具有低的物理記憶體。 這是通過 OS 的記憶體不足通知或主機指示的記憶體不足檢測出來。
- 由托管堆上已分配的對象使用的記憶體超出了可接受的閾值。 隨著進程的運行,此閾值會不斷地進行調整。
- 調用GC.Collect方法。 幾乎在所有情況下,你都不必調用此方法,因為垃圾回收器會持續運行。 此方法主要用於特殊情況和測試。
-
垃圾回收過程中會有哪些操作?
在垃圾回收啟動之前,除了觸發垃圾回收的線程,其它以外的所有托管線程都會被掛起。下圖微軟官方文檔演示了觸發垃圾回收並導致其他線程掛起的線程。
- 標記階段,找到並創建所有活動對象的列表。
- 重定位階段,用於更新對將要壓縮的對象的引用。
- 壓縮階段,用於回收由死對象占用的空間,並壓縮幸存的對象。 壓縮階段將垃圾回收中幸存下來的對象移至段中時間較早的一端。因為第 2 代回收可以占用多個段,所以可以將已提升到第 2 代中的對象移動到時間較早的段中。 可以將第 1 代幸存者和第 2 代幸存者都移動到不同的段,因為它們已被提升到第 2 代。
- 垃圾回收器使用以下信息來確定對象是否為活動對象:
- 堆棧根。 由實時 (JIT) 編譯器和堆棧查看器提供的堆棧變數。 JIT 優化可以延長或縮短報告給垃圾回收器的堆棧變數內的代碼的區域。
- 垃圾回收句柄。 指向托管對象且可由用戶代碼或公共語言運行時分配的句柄。
- 靜態數據。 應用程式域中可能引用其他對象的靜態對象。 每個應用程式域都會跟蹤其靜態對象。
-
-
CLR中垃圾回收的分類
從 .NET Framework 4.5 開始,後臺垃圾回收可用於伺服器 GC。 伺服器 GC 是伺服器垃圾回收的預設模式。後臺工作區域垃圾回收使用一個專用的後臺垃圾回收線程,而後臺伺服器垃圾回收使用多個線程。 通常一個邏輯處理器有一個專用線程。不同於工作站後臺垃圾回收線程,這些後臺伺服器 GC 線程不會超時。
-
工作站垃圾回收
工作站垃圾回收 (GC) 是為客戶端應用設計的。 它是獨立應用的預設 GC 風格。對於托管應用(例如由 ASP.NET 托管的應用),由主機確定預設 GC 風格。工作站垃圾回收既可以是併發的,也可以是非併發的。 併發(或後臺 )垃圾回收使托管線程能夠在垃圾回收期間繼續操作。後臺垃圾回收替換 .NET Framework 4 及更高版本中的並行垃圾回收。 工作站垃圾回收使用用於只有一個處理器的電腦。
-
伺服器垃圾回收
- 伺服器垃圾回收主要用於需要高吞吐量和可伸縮行的伺服器應用程式例如(WebApi)這種,在.Net Core和Framework4.5之後的版本中,伺服器垃圾回收既可以是非併發也可以是後臺執行的。
- 服務垃圾回收會為每個CPU提供一個用於執行垃圾回收的一個堆和專用線程,並能同時回收這些堆。每個堆都包含一個小對象堆和一個大對象堆,並且所有的堆都可由用戶代碼訪問。 不同堆上的對象可以相互引用。
- 因為多個垃圾回收線程一起工作,所以對於相同大小的堆,伺服器垃圾回收比工作站垃圾回收更快一些。
- 伺服器垃圾回收通常具有更大的段。 但是,這是通常情況:段大小特定於實現且可能更改。 調整應用程式時,不要假設垃圾回收器分配的段大小。
- 伺服器垃圾回收會占用大量資源。 例如,假設在一臺有 4 個處理器的電腦上,運行著 12 個使用伺服器 GC 的進程。 如果所有進程碰巧同時回收垃圾,它們會相互干擾,因為將在同一個處理器上調度 12 個線程。 如果進程處於活動狀態,則最好不要讓它們都使用伺服器 GC。
-
並行垃圾回收
- 併發垃圾回收通過最大程度地減少因回收引起的暫停,使交互應用程式能夠更快地響應。 在運行併發垃圾回收線程的大多數時間,托管線程可以繼續運行。 此設計使得在發生垃圾回收時的暫停時間更短。
- 併發垃圾回收在一個專用線程上執行。 預設情況下,CLR 將運行工作站垃圾回收,併在單處理器和多處理器電腦上同時啟用併發垃圾回收。
-
前臺和後臺的垃圾回收區別
垃圾回收分為前臺和後臺所謂的後臺回收是指 (gen2這一代所需要回收的對象還有大對象堆,大對象堆不會compact)那麼前臺回收說的就是gen0和gen1這一代所需要回收的對象了。
-
前臺垃圾回收
發生前臺垃圾回收的時候所有的托管線程會處於掛起階段,也就是說這個時候是GC線程執行的階段,其他線程不處理任務。另外就是前臺的垃圾回收也是由後臺垃圾回收線程去執行的,它是這麼做的如果後臺垃圾回收的線程在檢測是否有前臺發起垃圾回收指令,如果收到了就掛起後臺的垃圾回收的線程,執行前臺的垃圾回收的線程。在前臺垃圾回收完成之後,專用的後臺垃圾回收線程和用戶線程將繼續。 -
後臺垃圾回收
後臺垃圾回收可以消除併發垃圾回收所帶來的分配限制,因為在後臺垃圾回收期間,可發生暫時垃圾回收。 後臺垃圾回收可以刪除暫存世代中的死對象。如果需要,它還可以在第1代垃圾回收期間擴展堆。
-
-
-
大對象堆(LOH)和小對象堆分別是什麼?
載入 CLR 時,GC 分配兩個初始堆段:一個用於小型對象(小型對象堆或 SOH),一個用於大型對象(大型對象堆)。通過將托管對象置於這些托管堆段上來滿足分配請求。 如果該對象小於 85kb,則將它置於 SOH 的段上,否則,將它置於 LOH 段。 觸發垃圾回收後,GC 將尋找存在的對象並將它們壓縮。 但是由於壓縮費用很高,GC 會掃過 LOH,列出沒有被清除的對象列表以供以後重新使用,從而滿足大型對象的分配請求。 相鄰的被清除對象將組成一個自由對象。用戶代碼只能在第 0 代(小型對象)或 LOH(大型對象)中分配。只有 GC 可以在第 1 代(通過提升第 0 代回收未處理的對象)和第 2 代(通過提升第 1 代和第 2 代回收未處理的對象)中“分配”對象。- .Net GC將需要回收的對象分為了大對象堆和小對象堆,如果是大對象的話那麼它的某些特性比小對象顯得更為重要,例如複製一個對象到記憶體堆的其他位置的性能消耗會相當高,因此GC 會直接將大對象放置到大對象堆上面,大對象和小對象的區分預設是以85kb來作為區分,這個數字是可改的。
- 小對象一般是指第0代和第1代中的對象,這類對象在GC 回收的時候執行效率會很高,所以為了優化GC 回收器的性能所以才會區分代數,大多數對象都會通過第 0 代GC 回收進行回收,所以不會保留到下一代。大對象堆的回收時機是隨著2代GC 回收而執行,2代GC也是完整GC。
-
大對象堆在什麼情況會啟動垃圾回收?
- 分配超出第0代或者大對象閥值。
- 調用GC.Collect方法。
- 系統處於記憶體不足的狀況下同樣也會回收。
-
為什麼會存在大對象堆?
- 分配成本。
- 回收成本。
- 具有引用類型的數組元素。
-
GC / IDisposable / 析構函數三者的關係?
- GC是負責管理托管資源的記憶體的,它負責不存在引用地址對象的記憶體。
- 析構函數和IDisposable的介面的區別:析構是沒有執行順序的,析構函數的執行時機無法確定,所以官方給提供了IDisposable的介面,讓開發人員可以在代碼中顯示的調用Dispose釋放方法。
-
記憶體溢出如何發現和解決?
-
▼-----------------------------------------▼-----------------------------------------▼