程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#值類型和引用類型的內存管理

C#值類型和引用類型的內存管理

編輯:C#入門知識

本次日志,我們來重點聊一聊軟件開發過程中,如何提高性能方面的問題。這是軟件開發或研發過程中深層次的問題,這篇文章主要從內存分配和內存回收兩方面說明,我們軟件代碼編寫過程中,計算如何來工作的。在此你可以了解內存管理的過程和方式,以便在以後的軟件開發中注意它、利用它。

值類型包括:int,float,double,bool,結構,引用,表示對象實例的變量

引用類型包括:類和數組;比較特殊的引用類型string、object

一般情況下:值類型存儲在堆棧中(不包括包含在引用中的值類型,如類的值字段,類中的引用字段,數組的元素這些都是隨引用存儲在受管制的堆中);引用類型存儲在受管制的堆中,為什麼說是受管制的堆,下面會具體來談。

幾個概念:

虛擬內存:32位的計算機,每個進程擁有4G的虛擬內存。

受管制的堆(托管堆):受誰的管制?當然是無用單元收集器,即垃圾收集器。如何管理下面再說?

無用單元收集器:垃圾收集器除了會壓縮托管堆、更新引用地址、還會維護托管堆的信息列表等等。

關於值類型的存儲先看如下代碼:

{

int age=20;

double salary=2000;

}

上面定義的兩個變量,int age,告訴編譯器需要給我分配4個字節的內存空間來存儲age值,它是存儲在堆棧上的。堆棧屬入先進後出的數據結構,堆棧是從高位地址到低位地址存儲數據的。計算機寄存器中保持著一個堆棧指針,他總是指向堆棧最底端的自由空間地址,當我們定義一個int類型的值時,堆棧指針遞減四個字節的地址;當變量出了作用域後,堆棧指針相應的相應的遞增四個字節的地址,它只是堆棧指針的上下移動,所以堆棧的性能是相當高的。

下面看一下引用類型的存儲,還是先看代碼:

{

Customer customerA;

customerA=new Customer(); //假定Customer實例占據32個字節

}

上面的代碼第一行先聲明了一個Customer引用,引用的名字為customerA,在堆棧上給此引用分配存儲空間,存儲空間的大小為4個字節,因為它只存儲了一個引用,這個引用所指向的才是即將存儲Customer實例的空間地址,注意此時customerA並沒有指向具體的空間,它只是分配了一個空間而已。

第二行執行過程中,.net環境會搜索托管堆,尋找第一個未使用的、連續的32個字節空間分配給類的實例,並設置customerA指向這段空間的頂端位置(堆的空間是從低到高使用的)。當引用變量出作用域後,堆棧中的引用會無效,當托管堆中的實例還在,直到垃圾收集器對其進行清理。

到這裡細心的讀者可能會有些疑問,是不是定義引用類型時,計算機要搜索整個堆,尋找足夠大的內存空間來存儲對象呢?這樣會不會效率很低?如果沒有足夠大的連續的空間呢?這個就要談到“托管”了。堆是受垃圾收集器管理的,.net在執行垃圾收集器時釋放能釋放的所有對象,並壓縮其他對象,然後把所有自由空間組合在一起移動到堆的頂端, 形成連續的塊,同時更新其他移動對象的引用。如果再有對象定義,可以很快找到合適的空間。如此看來托管堆工作方式與堆棧類似,它是通過堆指針來完成空間的分配和回收的。

上面談了.net對內存空間分配的管理過程和方式,接下來談一談對內存的回收過程。談到資源的清理,不得不提到的兩個概念和三個方法。

兩個概念為:托管資源和非托管資源。

托管資源接受.net framework的CLR(通用語言運行時)的管理;非托管資源則不受它的管理。

三個方法為:Finalize(),Dispose(),Close()。

一、Finalize()為析構方法,清除非托管資源。

在類中定義方式:

public ClassName{

~ClassName()

{

//清理非托管資源(如關閉文件和數據庫聯接等)

}

}

它的特點是:

1. 運行不確定性。

它是受垃圾收集器的管理,當垃圾收集器工作時,會調用此方法。

2. 性能開銷大。

垃圾收集器工作方式為,對象如果執行了Finalize()方法,垃圾收集器第一次執行時,會把它放在一個特殊的隊列中;第二次執行的時候才會刪除此對象。

3. 不能顯示定義和調用,定義為析構方法形式。

基於以上特點,最好不要執行Finalize()方法,除非類確實需要它或與其他兩個方法結合來用。

二、Dispose()方法,可清除一切需要清除的資源,包括托管和非托管資源。

定義如下:

public void Dispose()

{

//清理應該清理的資源(包括托管和非托管資源)

System.GC.SuppressFinalize(this); //這一句很重要,下面會解釋原因。

}

它的特點:

1. 任何客戶代碼都應顯示調用這個方法,來釋放資源。

2. 由於第一點的原因,一般要做一個備份,這個備份一般由析構方法來擔任角色。

3. 定義此方法的類,必須繼承IDisposable接口。

4. 語法關鍵字using等同於調用Dispose(),使用using時,它是默認調用Dispose()方法。所以使用using的類也要繼承IDisposable接口。

Dispose()方法比較靈活,在資源不需要時立即釋放。它是資源的最終處理,調用它意味著會最終刪除對象。

三、Close()方法,暫時處置資源的狀態,可能以後還會使用。一般處理非托管資源。

定義如下:

public viod Close()

{

//對非托管資源狀態的設置,如關閉文件或數據庫連接

}

它的特點:

對非托管資源狀態的設置,一般是關閉文件或數據庫連接。

 

下面寫一個綜合且又經典的的例子,利用代碼演示一下各部分的作用:(為了省事,這個例子是從網上杜撰來的,只要說明問題就可以了,你說呢。在此應該感謝代碼原創者,感謝他寫了這麼經典和易懂的代碼,我們受益匪淺!)


public class ResourceHolder : System.IDisposable

{

public void Dispose()

{

Dispose(true);

System.GC.SuppressFinalize(this);

// 上面一行代碼作用是防止"垃圾回收器"調用這個類中的析構方法

// " ~ResourceHolder() "

// 為什麼要防止呢? 因為如果用戶記得調用Dispose()方法,那麼

// "垃圾回收器"就沒有必要"多此一舉"地再去釋放一遍"非托管資源"了

// 如果用戶不記得調用呢,就讓"垃圾回收器"幫我們去"多此一舉"吧 ^_^

// 你看不懂我上面說的不要緊,下面我還有更詳細的解釋呢!

}

 

protected virtual void Dispose(bool disposing)

{

if (disposing)

{

// 這裡是清理"托管資源"的用戶代碼段。

}

// 這裡是清理"非托管資源"的用戶代碼段。此處為析構方法的實際執行代碼,為了避免客戶代碼忘記顯示調用Dispose()方法,所作的備份。

}

 

~ResourceHolder()

{

Dispose(false); // 這裡是清理"非托管資源"

}

}

 

如果看不明白以上代碼,一定要仔細閱讀以下解釋,很經典,不看會後悔呦。

這裡,我們必須要清楚,需要用戶調用的是方法Dispose()而不是方法Dispose(bool),然而,這裡真正執行釋放工作的方法卻並不是Dispose(),而是Dispose(bool) ! 為什麼呢?仔細看代碼,在Dispose()中,調用了Dispose(true),而參數為"true"時,作用是清理所有的托管資源和非托管資源;大家一定還記得我前面才說過,"使用析構方法是用來釋放非托管資源的",那麼這裡既然Dispose()可以完成釋放非托管資源的工作,還要析構方法干什麼呢? 其實,析構方法的作用僅僅是一個"備份"!

為什麼呢?

格地說,凡執行了接口"IDisposable"的類,那麼只要程序員在代碼中使用了這個類的對象實例,那麼早晚得調用這個類的Dispose()方法,同時,如果類中含有對非托管資源的使用,那麼也必須釋放非托管資源! 可惜,如果釋放非托管資源的代碼放在析構方法中(上面的例子對應的是 " ~ResourceHolder() "),那麼程序員想調用這段釋放代碼是不可能做到的(因為析構方法不能被用戶調用,只能被系統,確切說是"垃圾回收器"調用),所以大家應該知道為什麼上面例子中"清理非托管資源的用戶代碼段"是在Dispose(bool)中,而不是~ResourceHolder()中! 不過不幸的是,並不是所有的程序員都時刻小心地記得調用Dispose()方法,萬一程序員忘記調用此方法,托管資源當然沒問題,早晚會有"垃圾回收器"來回收(只不過會推遲一會兒),那麼非托管資源呢?它可不受CLR的控制啊!難道它所占用的非托管資源就永遠不能釋放了嗎? 當然不是!我們還有"析構方法"呢! 如果忘記調用Dispose(),那麼"垃圾回收器"也會調用"析構方法"來釋放非托管資源的!(多說一句廢話,如果程序員記得調用Dispose()的話,那麼代碼"System.GC.SuppressFinalize(this);"則可以防止"垃圾回收器"調用析構方法,這樣就不必多釋放一次"非托管資源"了) 所以我們就不怕程序員忘記調用Dispose()方法了。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved