雖然在.NET編程過程中,絕大多數內存垃圾回收由CLR(公共語言運行時)自動回收,但也有很多需要我們編碼回收。掌握托管與非托管的基本知識,可以有效避免某些情況下導致的程序異常。
1.托管與非托管
1.1什么是托管與非托管?
托管代碼就是Visual Basic .NET和C#編譯器編譯出來的代碼。編譯器把代碼編譯成中間語言(IL),而不是能直接在你的電腦上運行的機器碼。中間語言被封裝在一個叫程序集(assembly)的文件中,程序集中包含了描述你所創建的類,方法和屬性(例如安全需求)的所有元數據。你可以拷貝這個程序集到另一台服務器上部署它。通常來說,這個拷貝的動作就是部署流程中唯一的一個操作。
托管代碼在公共語言運行庫(CLR)中運行。這個運行庫給你的運行代碼提供各種各樣的服務,通常來說,他會加載和驗證程序集,以此來保證中間語言的正確性。當某些方法被調用的時候,運行庫把具體的方法編譯成適合本地計算機運行的機械碼,然后會把編譯好的機械碼緩存起來,以備下次調用(這就是即時編譯)。隨着程序集的運行,運行庫會持續地提供各種服務,例如安全,內存管理,線程管理等等。這個程序被“托管”在運行庫中。Visual Basic .NET和C#只能產生托管代碼。如果你用這類語言寫程序,那么所產生的代碼就是托管代碼。
托管資源:一般是指被CLR(公共語言運行時)控制的內存資源,這些資源由CLR來管理。可以認為是.net 類庫中的資源。
非托管資源:不受CLR控制和管理的資源。
對於托管資源,GC負責垃圾回收。對於非托管資源,GC可以跟蹤非托管資源的生存期,但是不知道如何釋放它,這時候就要人工進行釋放。
1.2哪些資源是非托管的?
非托管代碼就是在Visual Studio .NET 2002發布之前所創建的代碼,例如Visual Basic 6, Visual C++ 6。 最糟糕的是,連那些依然殘存在你的硬盤中、擁有超過15年歷史的陳舊C編譯器所產生的代碼都是非托管代碼。非托管代碼直接編譯成目標計算機的機械碼,這些代碼只能運行在編譯出它們的計算機上,或者是其它相同處理器或者幾乎一樣處理器的計算機上。非托管代碼不能享受一些運行庫所提供的服務,例如安全和內存管理等。如果非托管代碼需要進行內存管理等服務,就必須顯式地調用操作系統的接口,通常來說,它們會調用Windows SDK所提供的API來實現。就最近的情況來看,非托管程序會通過COM接口來獲取操作系統服務。跟Visual Studio平台的其他編程語言不一樣,Visual C++可以創建非托管程序。當你創建一個項目,並且選擇名字以MFC,ATL或者Win32開頭的項目類型,那么這個項目所產生的就是非托管程序。
總而言之,非托管代碼是運行在公共語言運行庫環境(CLR)的外部,由操作系統直接執行的代碼。非托管代碼必須提供自己的垃圾回收、類型檢查、安全支持等服務;它與托管代碼不同,后者從公共語言運行庫中獲得這些服務。
總體來說就是 不受CLR控制和管理的資源
包括:比如文件流、圖像圖形類、數據庫的連接,網絡連接,系統的窗口句柄,打印機資源等,這類資源一般不存在堆上。可以認為操作系統資源的一組API。
原則:如果我們的類使用的非托管資源,如數據庫連接、文件句柄,這些資源需做到:使用后立刻釋放。
1.3托管代碼的執行過程
- 選擇編譯器:為獲得公共語言運行庫提供的優點,必須使用一個或多個針對運行庫的語言編譯器,如 Visual Basic、C#、Visual C++、JScript 或許多第三方編譯器(如 Eiffel、Perl 或 COBOL 編譯器)中的某一個。由於運行庫是一個多語言執行環境,因此它支持各種數據類型和語言功能。您所用的語言編譯器首先確定可用的運行庫功能,然后使用這些功能設計代碼。編譯器(而不是運行庫)建立代碼必須使用的語法。如果您的組件必須完全能夠被用其他語言編寫的組件使用,您的組件的導出類型必須只公開公共語言規范 (CLS) 中包括的語言功能。
- 編譯,將源代碼翻譯為microsoft中間語言(MSIL)並生成所需的元數據。
- 在執行時,實時 (JIT) 編譯器將 MSIL 翻譯為本機代碼。在此編譯過程中,代碼必須通過驗證過程,該過程檢查 MSIL 和元數據以查看是否可以將代碼確定為類型安全。
- 運行代碼:公共語言運行庫提供使執行能夠發生以及可在執行期間使用的各種服務的結構。
2.如何釋放非托管資源?
.NET對於釋放資源,標准做法如下:
(1)繼承IDisposable接口;
(2)實現Dispose()方法,在其中釋放托管資源和非托管資源,並將對象本身從垃圾回收器中移除(垃圾回收器不在回收此資源);
(3) 實現類析構函數,在其中釋放非托管資源。
對於Dispose()方法的幾個參數說明:
A. 參數為true表示釋放所有資源,只能由使用者調用
參數為false表示釋放非托管資源,只能由垃圾回收器自動調用
C. 如果子類有自己的非托管資源,可以重載這個函數,添加自己的非托管資源的釋放
D.但是要記住,重載此函數必須保證調用基類的版本,以保證基類的資源正常釋放
2.1Dispose的實現方法
...有時間再寫..
2.2舉例說明資源釋放
2.2.1 數據庫連接釋放
(1)錯誤做法:
SqlConnection conn = new SqlConnection(); //do something; conn.Dispose();
此段代碼,如果出現異常conn.Dispose()未能及時執行,會導致沒有及時關閉連接。
(2)正確做法:
SqlConnection conn = new SqlConnection(); try { //do something; } finally { conn.Dispose(); }
對於以上代碼,我們還可以簡化代碼量,c#使用using簡化輸入,編譯器自動翻譯成 try...finally,改為下面寫法
using(SqlConnection conn = new SqlConnection()) { //do something; }
2.3對於釋放資源注意的幾點
A. 顯示調用Dispose()方法,可以及時的釋放資源,同時通過移除Finalize()方法的執行,提高了性能;
B. 如果沒有顯式調用Dispose()方法,垃圾回收器也可以通過析構函數來釋放非托管資源,垃圾回收器本身就具有回收托管資源的功能,從而保證資源的正常釋放,只不過由垃圾回收器回收會導致非托管資源的未及時釋放的浪費。
C. 在.NET中應該盡可能的少用析構函數釋放資源。在沒有析構函數的對象在垃圾處理器一次處理中從內存刪除,但有析構函數的對象,需要兩次,第一次調用析構函數,第二次刪除對象。而且在析構函數中包含大量的釋放資源代碼,會降低垃圾回收器的工作效率,影響性能。
D. 對於包含非托管資源的對象,最好及時的調用Dispose()方法來回收資源,而不是依賴垃圾回收器
E. 析構函數只能由垃圾回收器調用。
F. Despose()方法只能由類的使用者調用。
G. 在.NET中,凡是繼承了IDisposable接口的類,都可以使用using語句,從而在超出作用域后,讓系統自動調用Dispose()方法。
H. 一個資源安全的類,都實現了IDisposable接口和析構函數。提供手動釋放資源和系統自動釋放資源的雙保險。
2.4 關於Finalize和Dispose
(1)、Finalize只釋放非托管資源;
(2)、Dispose釋放托管和非托管資源;
(3)、重復調用Finalize和Dispose是沒有問題的;
(4)、Finalize和Dispose共享相同的資源釋放策略,因此他們之間也是沒有沖突的。
==========================【Origin and Reference】==========================
https://www.cnblogs.com/yubinfeng/p/4625833.html
https://www.cnblogs.com/yangecnu/archive/2013/04/30/3052652.html
https://www.2cto.com/kf/201007/52838.html