共用方式為


自動記憶體管理

自動記憶體管理是 Common Language Runtime 在 Managed Execution 期間所提供的其中一項服務。 Common Language Runtime 的垃圾回收器會管理應用程式的記憶體配置和釋放。 對於開發人員而言,這表示您不需要撰寫程式代碼,即可在開發受控應用程式時執行記憶體管理工作。 自動記憶體管理可以消除常見問題,例如忘記釋放物件並造成記憶體流失,或嘗試存取已釋放之物件的記憶體。 本節介紹垃圾收集器如何分配和釋放記憶體。

配置記憶體

當您初始化新的處理序 (Process) 時,Runtime 會保留一塊連續的位址空間區域,供處理序使用。 這塊保留的位址空間稱為 Managed 堆積 (Heap)。 Managed 堆積會保留即將配置給堆積中下一個物件的位址指標。 剛開始會將這個指標設定為 Managed 堆積的基底位址 (Base Address)。 所有 參考類型 都會配置在受控堆上。 當應用程式建立第一個參考型別時,會為該型別配置 Managed 堆積基底位址的記憶體。 當應用程式建立下一個物件時,垃圾收集器會在緊接著第一個物件之後的位址空間中配置記憶體。 只要位址空間可用,垃圾收集行程就會以這種方式持續為新物件分配空間。

從 Managed 堆積中配置記憶體要比 Unmanaged 記憶體配置快。 因為執行階段會藉由將值加到指標上來配置物件的記憶體,所以它幾乎和從堆疊配置記憶體一樣快。 此外,因為新物件會被連續儲存在託管堆積中,所以應用程式能快速地存取這些物件。

釋放記憶體

記憶體回收行程的最佳化引擎會根據所做的配置,決定執行回收的最佳時機。 當記憶體回收行程執行回收時,會將應用程式已經不再使用的物件記憶體釋放出來。 它會藉由檢查應用程式的根目錄,判斷不再使用哪些物件。 每個應用程式都有一組根目錄。 每一個根目錄都會參考 Managed 堆積上的物件,要不然就是設定為 Null。 應用程式的根目錄包括靜態字段、線程堆疊上的局部變數和參數,以及 CPU 快取器。 垃圾收集器可以存取 JIT 編譯器 和執行階段維護的活動根目錄清單。 使用此清單,它會檢查應用程式的根目錄,並在程式中建立圖形,其中包含可從根存取的所有物件。

不在圖形中的物件無法從應用程式的根目錄連線。 垃圾收集器會將無法到達的物件視為垃圾,並釋放其佔用的記憶體。 在回收期間,記憶體回收行程會檢查 Managed 堆積,尋找無法取得的物件所佔用的位址空間區塊。 每找到一個無法取得的物件時,它便會使用記憶體複製功能,壓縮記憶體中可取得的物件,然後釋放出為無法取得的物件所配置的位址空間區塊。 一旦壓縮完可取得物件的記憶體之後,記憶體回收行程會進行必要的指標更正,使應用程式的根目錄指向新位置中的物件。 它也會將 Managed 堆積的指標放在最後取得物件的後面。 請注意,只有在集合發現大量不可達的物件時,記憶體才會被壓縮。 如果受控堆疊中的所有物件在垃圾回收後依然存在,則不需要記憶體整理。

為了改善效能,Runtime 會為大型物件配置不同堆積中的記憶體。 記憶體回收行程會自動釋放大型物件的記憶體。 不過,為了避免在記憶體中移動大型物件,不會壓縮此記憶體。

世代和效能

為了優化垃圾回收器的效能,管理堆積分成三個世代:0、1 和 2。 執行期的垃圾回收演算法是基於電腦軟體產業在實驗不同垃圾回收方案時發現的幾個概括性結論。 首先,壓縮受控堆積中部分記憶體比壓縮整個受控堆積更快。 其次,較新的物件會有較短的存留期,而較舊的物件會有較長的存留期。 最後,較新的物件通常會彼此相關,並由應用程式在同一時間存取。

執行期的垃圾收集器會將新物件儲存在第0代中。 在應用程式存留期初期建立的物件則會升階並儲存在第 1 個和第 2 個層代。 在本主題中稍後將描述物件升級的流程。 由於壓縮部分受控堆的速度比整個堆還快,此機制可讓垃圾收集器在特定世代中釋放記憶體,而不必每次執行收集時釋放整個受控堆的記憶體。

事實上,當代 0 已滿時,垃圾回收器會執行回收作業。 如果應用程式在第 0 代已滿時嘗試建立新的物件,垃圾回收器會發現第 0 代中沒有剩餘地址空間來配置給物件。 垃圾收集行程會嘗試為 物件釋放層代 0 中的地址空間,以執行收集。 記憶體回收行程是從檢查第 0 個層代中的物件開始,而不是檢查 Managed 堆積中的所有物件。 這是最有效率的方法,因為新物件通常會有較短的存留期,而且預期執行集合時,應用程式不會再使用第0代中的許多物件。 此外,單靠層代 0 的集合通常會回收足夠的記憶體,讓應用程式繼續建立新的物件。

垃圾收集器在進行第 0 代的收集之後,它會壓縮可達物件的記憶體,如本主題先前所述的 釋放記憶體。 垃圾收集器接著會升級這些物件,並將受控堆的這個部分視為第 1 代。 由於回收之後存留下來的物件通常具有較長的存留期,因此才會將它們提升至較高的層代。 因此,垃圾收集器在每次執行第 0 代的收集時,不需要重新檢查第 1 代和第 2 代中的物件。

在垃圾收集器執行第一次第 0 代的收集並將可到達的物件提升到第 1 代之後,其餘的託管堆積被視為第 0 代。 它會繼續為層代 0 中的新物件配置記憶體,直到層代 0 已滿,而且必須執行另一個集合。 此時,垃圾回收器的優化引擎會判斷是否需要檢查較舊世代中的物件。 例如,如果第 0 代的集合未回收足夠的記憶體,讓應用程式順利完成建立新對象的嘗試,垃圾收集器可以執行第 1 代的集合,然後再執行第 2 代的集合。 如果這無法回收足夠的記憶體,垃圾回收器可以執行第 2、1 和 0 代的收集。 每次收集之後,垃圾回收器會整理世代 0 中的可達物件,並將其升階為世代 1。 在回收之後存留下來的第 1 個層代物件則會提升至第 2 個層代。 因為垃圾回收器只支援三個世代,所以在第 2 代中倖存下來的物件會保留在第 2 代中,直到它們在未來的回收中被確定無法被存取為止。

釋放非受控資源的記憶體

針對應用程式所建立的大部分物件,您可以依賴垃圾收集器自動執行必要的記憶體管理工作。 但是,Unmanaged 資源需要明確清除。 最常見的 Unmanaged 資源類型就是包裝作業系統資源 (例如檔案控制代碼、視窗控制代碼或網路連接) 的物件。 雖然垃圾收集器能夠追蹤封裝非管理資源之管理物件的存留期,但它並沒有具體知識來清除該資源。 當您建立封裝 Unmanaged 資源的物件時,建議您提供必要的程式代碼,以在公用 Dispose 方法中清除 Unmanaged 資源。 藉由提供 Dispose 方法,您可以讓用戶在物件完成時明確釋放其記憶體。 當您使用封裝非受控資源的物件時,您應該注意 Dispose 並在需要時呼叫它。 如需有關清理非受控資源的詳細資訊,以及實作 Dispose 的設計模式範例,請參閱 記憶體管理

另請參閱