王紅偉+王紅紀
摘 要:在面向對象的程序環(huán)境中,任何一個程序都需要使用到計算機資源,程序中的每一個類型都代表著程序需要的資源。程序在使用這些資源過程中,要經(jīng)歷一個分配內(nèi)存給類型資源、初始化內(nèi)存及數(shù)據(jù)類型、訪問數(shù)據(jù)成員信息、銷毀并清理資源、釋放內(nèi)存的過程。
關鍵詞:.NET 內(nèi)存分配 垃圾回收器 內(nèi)存釋放
中圖分類號:TP311 文獻標識碼:A 文章編號:1672-3791(2017)01(b)-0028-02
在.NET中,內(nèi)存中的資源(即所有二進制信息的集合)分為“托管資源”和“非托管資源”。托管資源必須接受.NET的CLR(通用語言運行時,.NET框架的底層)的管理(諸如內(nèi)存類型安全性檢查),而非托管資源則不必接受.NET的通用語言運行時的管理。.NET對內(nèi)存的管理首先是管理托管資源和非托管資源所占用的內(nèi)存分配和釋放;其次是尋找不再使用的對象,釋放其占用的內(nèi)存,以及釋放非托管資源所占用的內(nèi)存;最后是釋放內(nèi)存之后,出現(xiàn)了內(nèi)存碎片,垃圾回收器移動一些對象,以得到整塊的內(nèi)存,同時所有的對象引用都將被調整為指向對象新的存儲位置。
1 內(nèi)存分配
.NET平臺引入高效的、安全的托管執(zhí)行環(huán)境——通用語言運行時。通用語言運行時管理內(nèi)存的區(qū)域,主要有三塊,依次是:(1)線程的堆棧,用于分配值類型實例對象,它主要由操作系統(tǒng)管理,當值類型實例所在方法結束時,其存儲單位自動釋放,堆棧的執(zhí)行效率高,但存儲容量有限。(2)GC堆(中文名稱垃圾回收,是.NET中對內(nèi)存管理的一種功能),用于分配小對象實例。(3)LOH(Large Object Heap)堆,用于分配大對象實例。
.NET CLR將所有資源分配到托管堆上,當一個線程初始化,運行時將預定一塊未使用連續(xù)的地址空間。這塊地址空間就是托管堆。堆中同時維護著一個指針,我們叫它下一個對象指針。這個指針告訴我們下一個程序對象將被分配到堆中的什么位置。在程序初期,這個指針被設置到最基本的內(nèi)存地址。使用new運算符創(chuàng)建對象時,運行庫都從托管堆為該對象分配內(nèi)存。只要托管堆中有地址空間可用,并且空間中夠用,下一個對象指針將指向堆中的此對象,對象構造函數(shù)被調用,最后返回對象內(nèi)存地址。
2 內(nèi)存釋放與回收
當一個程序使用new操作符創(chuàng)建一個新對象時,可能沒有足夠的地址空間來放置它。為了檢測地址空間是否足夠,托管堆會嘗試把對象放到下一個對象指針位置,如果下一個對象指針移動到超過地址空間邊界,那說明堆已滿,GC則進行垃圾回收。垃圾回收器跟蹤并回收托管內(nèi)存中分配的對象,定期執(zhí)行垃圾回收以回收分配給沒有有效引用的對象的內(nèi)存。當使用可用內(nèi)存不能滿足內(nèi)存請求時,垃圾回收會自動進行。在進行垃圾回收時,垃圾回收器首先搜索內(nèi)存中的托管對象,然后從托管代碼中搜索被引用的對象并標記為有效,接著釋放沒有被標記為有效的對象并收回內(nèi)存,最后整理內(nèi)存將有效對象挪動到一起。
3 內(nèi)存釋放與回收的模式
我們創(chuàng)建的類不包含非托管資源時,只需要直接使用,CLR自然會判斷其生命周期結束而后回收相應的托管資源。但如果我們創(chuàng)建了含有非托管資源的類,CLR提供其他機制來幫助自動釋放非托管資源。在.NET中提供三種模式來回收內(nèi)存資源:dispose模式、finalize方法、close方法。
(1)dispose提供一種顯式釋放內(nèi)存資源的方法。此方法用于更快更具操作性進行釋放,可以使用此方法。結構和類型都可以實現(xiàn)IDispose,因為是對象本身釋放非托管資源,所以可以用對象名來顯式的調用來實現(xiàn)內(nèi)存釋放。所有實現(xiàn)IDisposable接口的類對象都必須調用這一方法。.NET基類庫中許多類型都實現(xiàn)IDisposable接口,有時給這一方法提供另外的別名,例如:輸入輸出類中的Close方法。
(2)finalize方法是.NET的內(nèi)部的一個釋放內(nèi)存資源的方法,由垃圾回收器自己調用。此方法被不斷重寫,原因是一些類通過平臺調用服務或復雜的COM互操作性任務使用了非托管資源。對象類中也有這一方法,但創(chuàng)建的類不能重寫此方法,可以通過析構函數(shù)來達到同樣的效果。這一方法的作用是保證.NET對象能在垃圾回收時清除非托管資源。通用語言運行時在托管堆上分配對象時,運行庫自動確定該對象是否提供一個自定義的Finalize方法。如果是這樣,對象會被標記為可終結的,同時一個指向這個對象的指針被保存在名為終結隊列的內(nèi)部隊列中。終結隊列是一個由垃圾回收器維護的表,它指向每一個在從堆上刪除之前必須被終結的對象。Finalize最大作用是確保非托管資源一定被釋放。
(3)close和dispose其實一樣。一些類中沒有定義dispose的方法,只定義了close方法,而close實質上也是調用了一個私有的dispose方法,而finalize其實也是調用一個不對外公開的dispose方法。
4 內(nèi)存釋放與回收過程
垃圾回收時機:托管堆滿,內(nèi)存分配即將不足。程序員可以手動調用GC.Collect()。垃圾確認:通過根來尋找可達的對象,并做標記,然后回收沒有標記的對象。垃圾回收:內(nèi)存回收,實現(xiàn)了Finalize方法的對象用此方法實現(xiàn)內(nèi)存回收。內(nèi)存轉移、合并:垃圾回收后使得內(nèi)存不連續(xù)、零碎,.NET會將利用的內(nèi)存合并為連續(xù)的塊,然后更新對象的指針。
5 內(nèi)存釋放與回收時的注意事項
值類型(包括引用和對象實例)和引用類型的對象,當它們出了作用域后會自動釋放所占內(nèi)存。因為它們都保存在“堆?!敝?,這是一種先進后出數(shù)據(jù)結構。引用類型的引用所指向的對象實例保存在“堆”中,而堆因為是一個自由存儲空間,所以它并沒有像“堆?!蹦菢佑猩嫫冢ā岸褩!钡脑貜棾龊缶痛砩嫫诮Y束,也就代表釋放了內(nèi)存),“垃圾回收器”只對這塊區(qū)域起作用。 “垃圾回收器”并不會立即執(zhí)行(當堆中的資源需要釋放時),因為“垃圾回收器”的調用是比較消耗系統(tǒng)資源的,因此,不能經(jīng)常被調用。這時,用戶可以調用方法System.GC.Collect()來強制執(zhí)行“垃圾回收器”??蓪崿F(xiàn)Dispose()方法來顯式釋放由對象使用的所有未托管資源。垃圾收集器在釋放了它能釋放的所有對象后,就會壓縮其他對象,把他們都移動回堆的端部,再次形成一個連續(xù)的塊。
參考文獻
[1] Neo_Wu[EB/OL].http://blog.csdn.net/neo_ustc/article/details/12883185,2013.
[2] .Net垃圾回收中涉及的名稱[EB/OL].http://www.cnblogs.com/hfclytze/p/3706326.html,2014.
[3] binbingg[EB/OL].http://www.cnblogs.com/chinafine/articles/864776.html,2016.
[4] 賀俊峰..Net垃圾回收器管理應用程序的內(nèi)存分配和釋放[EB/OL].http://hejunfeng.blog.51cto.com/3182120/627153.
[5] http://jingyan.baidu.com/article/642c9d34d7ec8f644a46f7fb.html,2013.
[6] Depaul[EB/OL].http://blog.csdn.net/leewhoee/article/details/17291953,2014.