自動記憶體管理是公共語言執行時在託管執行過程中提供的服務之一。公共語言執行時的垃圾回收器為應用程式管理記憶體
的分配和釋放。對開發人員而言,在開發託管應用程式時不必編寫執行記憶體管理任務程式碼。
分配記憶體
初始化新程序時,執行時會為程序保留一個連續的地址空間區域。這個保留的地址空間被稱為託管堆。託管堆維護著一個指標,用它指向堆中分配的下一個物件的地址。初始時,該指標設定為指向託管堆的基礎地址。託管堆上包含所有的引用型別。應用程式在建立第一個引用型別時,將為託管堆的基址中的型別分配記憶體。應用程式建立下一個物件時,垃圾回收器在緊接第一個物件後面的地址空間內為它分配記憶體。只要地址空間可用,垃圾回收器就會繼續以這種方式為新物件分配空間。
在託管堆中分配記憶體要比非託管堆記憶體分配速度快。由於執行時通過為指標新增值來為物件分配記憶體,這個速度幾乎與堆疊中分配記憶體一樣快。另外由於連續分配的新物件在託管堆中是連續儲存,所以應用程式可以快速訪問這些物件。
釋放記憶體
垃圾回收器的優化引擎根據所執行的分配決定執行回收的最佳時間。垃圾回收器在執行回收時,會釋放應用程式不再使用的物件的記憶體。它通過檢查應用程式的根來確定不再使用的物件。每個應用程式都有一組根。每個根或者引用託管堆中的物件設定為空。應用程式的根包含執行緒堆疊上的靜態欄位、區域性變數和引數以及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()方法釋放。