C#教程(4)-----託管和非託管的資源(更新中......)

一、資源

資源是一個反覆被利用的術語。術語”資源“的一個用法是本地化。在本地化中,資源用於翻譯文本和圖像。”資源“的另一個用法用於主題:使用託管和非託管的資源——儲存在託管和本機堆中的資源。儘管垃圾收集器釋放儲存在託管堆中的託管對象,但不釋放本機堆中的對象。必須由開發人員自己釋放他們。

二、後臺內存管理

雖然C#編程不需要擔心具體的內存管理,但理解程序如何在後臺管理內存有助於提高性能和速度。

1.值數據類型

windows使用一個虛擬尋址系統,把程序的可用內存地址映射到真實的內存地址上。實際上每個進程都使用4GB的內存(32位處理器)。這個4GB包含了程序的所有代碼,dll和變量空間等。被稱爲虛擬地址空間或虛擬內存。
4GB中的每一個存儲單元都是從0往上排序。要訪問內存中的某個值就要提供表示這個空間的一個數字。編譯器將便於人們理解的變量轉換爲處理器可以理解的內存地址。
在虛擬內存中,有一個區域稱爲棧。工作原理,先進後出。(如圖)
在這裏插入圖片描述

{
	int a;
	{
		int b;
	}
}

在一段程序中先定義a再定義b,程序結束時會先釋放b,再釋放a,b的生命週期完全包含在a中。同時b在另一個代碼塊中(花括號),所在它在另一個作用域中。這稱爲塊作用域或結構作用域。
棧指針表示棧中下一個空閒存儲單元的地址。棧是由上向下填充。當數據進棧後,棧指針就會隨之進行調整。
例如:

			int a = 10;
            double b = 30.0;

在這裏插入圖片描述
int a = 10;之後爲a分配4個字節,即79999~ 79996;double佔8個字節,即79995~ 79988;

2. 引用數據類型

堆,全稱託管堆。是處理器可用內存的另一個內存區域。由下向上分配

假設有類Customer
Customer a;
a = new Customer();

代碼執行到Customer a;在棧上給Customer分配4個自接的空間,但這只是一個引用。執行到a = new Customer();時,會在堆中給a分配一個適當大小的空間已存儲Customer對象,並將棧中的a的值設置爲分配給Customer對象的內存地址。

3.垃圾回收

在這裏插入圖片描述
堆中不會出現這種情況,因爲垃圾回收器會將其他對象移動到堆的端部,再次形成一個連續的內存塊。這就是託管和非託管的堆的區別。使用託管堆就只需要讀取堆指針的值計科,不需要遍歷鏈表來查找一個地方放置數據。
注意:可以調用System.GC.Collect()強迫垃圾回收器在某個地方運行。

創建對象時,會將對象放在託管堆上。堆的第一部分稱爲第0代。創建新對象時,會將他移動到這個部分。因此這裏駐留了最新的對象。

垃圾回收過程中,遺留的舊對象會進行壓縮放在第1代對應的部分上。再次進行垃圾回收時,老對象的這種移動會再次發生,這意味着第一代變爲第二代,第0代對象移動到第1代,第0帶仍用來保存新對象。
注意:在給對象分配空間時,如果超過了第0代對應的部分的容量,就會進行垃圾回收。

這個過程會極大的提高應用程序的性能。一般而言,新創建的對象都是可以進行回收的,而且可能回收大量比較新的對象。如果這些對象是相鄰的也會使應用程序執行速度變快。

在.NET中,垃圾回收提高性能的另一領域是架構處理堆上較大對象的方式。在.NET中,大於85000個字節的對象有自己的託管堆,而不是在主堆上,稱爲大託管堆。

在進一步改進垃圾回收後,應用程序線程只會對第一代和第0代對象實施回收,而其他代放在後臺執行,縮短時間。

有助於垃圾回收的另一項優化是垃圾回收的平衡,它用於服務器的垃圾回收。對於服務器,每一個邏輯服務器都有一個垃圾回收堆。因此一個垃圾回收堆用盡了內存時,會調用垃圾回收程序,所有其他堆也會觸發垃圾回收程序,這就造成浪費。垃圾回收會平衡這些堆——小對象堆和大對象堆。推進這個平衡過程,可以減少不必要的回收。

三、強引用和弱引用

垃圾回收器不能回收仍在使用的對象——強引用。可以回收不在根表直接或間接引用的託管內存,但是可能會忘記釋放引用。

注意:如果對象相互引用——A引用B,B引用C,C引用A,則GC可能會銷燬所有對象。

計入有一個對象MyClass,並創建一個變量myclassVariable來引用它。那麼在這個變量的作用域內,就存在對MyClass的強引用

var myclassVariable = new MyClass();

這意味着垃圾回收器不能回收MyClass對象使用的內存。可以穿件一個緩存對象,它引用myclassVariable對象。

			var myCache = new MycaChe();
            myCache.Add(myclassVariable);

用完myclassVariable,可以將他指定爲NULL

myclassVariable = null;

現在如果運行垃圾回收器,就不能釋放myclassVariable引用的內存,因爲在緩存對象中使用。但這樣的引用很容易被忘記,使用WeakRaference避免。

注意:使用事件很容易錯過引用的清理。此時也可以使用弱引用

弱引用是使用WeakReference類創建的。使用構造函數,可以傳遞強引用。

var myWeakReference = new WeakReference(new dataobject);

            if (myWeakReference.IsAlive)
            {
                dataobject strongReference = myWeakReference.Target as dataobject;
                if (strongReference != null)
                {
                    //use the strongReference
                }
                else
                {
                    //reference not available
                }
            }

【弱引用這塊沒怎麼看懂,以後重新編寫此處,會的小夥伴可以教我一下嗎】

四、處理非託管的資源

C#編程中,不需要擔心不再需要的對象,垃圾回收器會完成這項任務。但是垃圾回收器不知帶如何處理非託管資源,例如:文件句柄、網絡連接和數據庫連接。託管類在封裝對非託管資源的引用時要制定專門的規則,確保回收實例時釋放。
兩種機制來解決這一問題:

  • 聲明析構函數
  • 實現System.IDisposable接口

1.析構函數或終結器

C#在執行析構函數時,它會隱式的將析構函數古代碼轉換爲等駕馭重寫Finallize()方法的代碼,從而確保執行父類的Finallize()方法;
C#使用析構函數比C++次數少的原因:

  1. C#析構函數不穩定
  2. 延遲對象從內存銷燬的時間
  3. 對性能的影響顯著

2.IDisposable接口

C#用IDisposable接口代替析構函數,不帶參數,返回void。

假設有有一個A類需要使用外部資源,使用它並銷燬他:

			var a = new A();

            //do somesthing

            a.Dispose();

如果怕出現異常:

			 A a = null;
            try
            {
                a = new A();

                //do somesthing
            }
            finally
            {
                a?.Dispose();
            }

3.using語句

使用異常處理,即使異常也能確保調用了Dispose()方法,但重複這樣的結構,代碼容易混淆。可以使用using語句,當實現IDisposable接口的語句的對象引用超出作用域時就會自動調用Dispose()方法。

using (var a = new A())
            {
                //do somesthing
            }

4.實現IDisposable接口和析構函數

舉一個實現IDisposable接口和析構函數的例子:

	public class UserPreferences:IDisposable
    {
        private bool _idDisposed = false;

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_idDisposed)
            {
                if (disposing)
                {
                    ///
                }
                ///
            }
            _idDisposed = true;
        }

        ~UserPreferences()
        {
            Dispose(false);
        }

        public void SomeMethod()
        {
            if (_idDisposed)
            {
                throw new ObjectDisposedException("ResourceHolder");
            }
        }
      }

上述例子中Dispose有第二個重構方法,這是真正完成清理工作的方法,由析構函數調用。
GC.SuppressFinalize(this);表示該對象已經清理,不需要再次清理。

5.IDisposable和終結器的規則

  • 如果類定義實現了IDisposable的成員,也應該實現IDisposable;
  • 實現IDisposable並不意味着實現終結器。要是香釋放本機資源,就需要終結器。
  • 如果實現了終結器,也應該實現IDisposable接口。
  • 在終結器的實現代碼中,不能訪問已終結的對象

五、不安全的代碼

1.用指針直接訪問內存

2.指針示例:PointerPlayground

3.使用指針優化性能

六、平臺調用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章