自動內存管理是公共語言運行時在托管執行過程中提供的服務之一。公共語言運行時的垃圾回收器為應用程序管理內存
的分配和釋放。對開發人員而言,在開發托管應用程序時不必編寫執行內存管理任務代碼。
分配內存
初始化新進程時,運行時會為進程保留一個連續的地址空間區域。這個保留的地址空間被稱為托管堆。托管堆維護着一個指針,用它指向堆中分配的下一個對象的地址。初始時,該指針設置為指向托管堆的基礎地址。托管堆上包含所有的引用類型。應用程序在創建第一個引用類型時,將為托管堆的基址中的類型分配內存。應用程序創建下一個對象時,垃圾回收器在緊接第一個對象后面的地址空間內為它分配內存。只要地址空間可用,垃圾回收器就會繼續以這種方式為新對象分配空間。
在托管堆中分配內存要比非托管堆內存分配速度快。由於運行時通過為指針添加值來為對象分配內存,這個速度幾乎與堆棧中分配內存一樣快。另外由於連續分配的新對象在托管堆中是連續存儲,所以應用程序可以快速訪問這些對象。
釋放內存
垃圾回收器的優化引擎根據所執行的分配決定執行回收的最佳時間。垃圾回收器在執行回收時,會釋放應用程序不再使用的對象的內存。它通過檢查應用程序的根來確定不再使用的對象。每個應用程序都有一組根。每個根或者引用托管堆中的對象設置為空。應用程序的根包含線程堆棧上的靜態字段、局部變量和參數以及CPU寄存器。垃圾回收器可以訪問由實時編輯器(JIT)和運行時維護的活動根的列表。垃圾回收器對照此列表檢查應用程序的根,並在此過程中創建一個圖表,在其中包含所有可從這個根中訪問的對象。
不在該圖表中的對象無法從應用程序的根中訪問。垃圾回收器會考慮無法訪問的對象垃圾,並釋放為他們分配內存。在回收中,垃圾回收器檢查托管堆,查找無法訪問對象占據的地址空間塊。發現無法訪問的對象時,它就使用復制功能來壓縮內存中可以訪問的對象。釋放分配給不可訪問對象的地址空間。在壓縮了可訪問對象的內存后,垃圾回收器就會做出指針更正,一邊應用程序的根指向新地址中的對象。它會將托管堆指針定位至最后一個可訪問對象以后。
注意:垃圾回收器只要發現大量無法訪問的對象時,才會壓縮內存。如果托管堆中的所有對象均未被回收,則不需要壓縮內存。
為了改進性能,運行時(JIT)為單獨堆中的大型對象分配內存。垃圾回收器會自動釋放大型對象的內存。為了避免動內存中大型對象,不會壓縮此內存。
級別和性能
為了優化垃圾回收器的性能,將托管堆分為三代:第0代,第1代,第2代。運行時的垃圾回收算法基於以下幾個普通原理,這些垃圾回收方案的原理已在軟件實驗中得到驗證。首先,壓縮托管堆的一部分內存要比壓縮整個托管堆速度塊。其次,比較新對象生存期比較短,而比較老的對象生存期比較長。最后,比較新的對象趨向於相互關聯,並同時由應用程序訪問。
運行時的垃圾回收器將新對象存儲在第0級中。在應用程序生存期的早期創建對象如果未被回收,則升級並存儲在第1級和第2級中。
實際上,垃圾回收器在第0級托管堆已滿時執行回收。如果應用程序在第0級托管堆已滿時嘗試新建對象,垃圾回收器將會發現第0級已沒有可分配給對象地址空間。垃圾回收器就執行回收操作。釋放第0級托管堆中的地址空間。垃圾回收器從第0級托管堆中的對象開始執行回收。
垃圾回收器執行第0級托管堆的回收后,會壓縮可訪問對象內存。然后垃圾回收器升級這些對象。並考慮第1級退管堆的這部分。因為未被回收的對象一般為比較長的生存期,所以將他們升級至更高級別。因此垃圾回收器每次回收第0級的托管堆,不會檢查第1級和第2級托管堆中的對象。
在執行第0級托管堆的首次回收並把可訪問的對象升級至第1級托管堆后,垃圾回收器將考慮第0級托管堆的其余部分。它將繼續為第0級的托管堆中的新對象分配內存,直至第0級托管堆無法在分配地址后在執行另外一次的垃圾回收為止。此時,垃圾回收器的優化引擎會決定是否需要檢測比較舊的級別中的對象。如:垃圾回收器發現0級托管堆中釋放后的內存,不能再創建新的對象,垃圾回收器就會執行第1級托管堆的回收,然后再執行第2級托管堆的回收。如果這樣仍不能回收足夠的內存,垃圾回收器將執行第2,1和0級托管堆的回收。每次回收后,垃圾回收器都會壓縮第0級托管堆中的可訪問對象並將他們升級到第1級托管堆。第1級托管堆中未被回收的對象將會升級到第2級托管堆。由於垃圾回收器只支持三個級別,因此第2級托管堆中未被回收的對象繼續保留再第2級托管堆中,直到在將來的回收中確定題目為無法訪問為止。
為非托管資源釋放內存
對於應用程序創建的大多數對象,可以依賴垃圾回收器自動執行必要的內存管理任務。但是,非托管資源需要顯示清除。最常用的非托管堆資源類型時包裝操作系統資源的對象,如:文件句柄,窗口句柄或網絡連接。雖然垃圾回收器可以跟蹤封裝非托管堆資源的托管對象的生存期,但卻無法具體了解如果清理資源。創建封裝非托管資源的對象時,建議在公共Dispose方法中提供必要的代碼用以清理非托管堆資源。通過提供Dispose方法,對象的用戶可以在使用完對象后顯示釋放內存。使用封裝非托管資源的對象時,應在不使用的時候調用Dispose()方法釋放。