首頁  >  文章  >  後端開發  >  C# 值型別和引用型別的記憶體管理

C# 值型別和引用型別的記憶體管理

巴扎黑
巴扎黑原創
2016-12-20 11:50:411757瀏覽

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

值類型包括:int,float,double,bool,結構,引用,表示物件實例的變數

引用型別包括:類別與陣列;比較特殊的參考型別string、object

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

幾個概念:

虛擬記憶體:32位元的計算機,每個進程擁有4G的虛擬記憶體。

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

無用單元收集器:垃圾收集器除了會壓縮託管堆、更新引用位址、還會維護託管堆的資訊清單等等。

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

{

int age=20;

double salary=2000;

}

}

分配4個位元組的記憶體空間來儲存age值,它是儲存在堆疊上的。堆疊屬入先進後出的資料結構,堆疊是從高位址到低位址儲存資料的。電腦暫存器中保持著一個堆疊指針,他總是指向堆疊最底端的自由空間位址,當我們定義一個int類型的值時,堆疊指標遞減四個位元組的位址;當變數出了作用域後,堆疊指標對應的對應的遞增四個位元組的位址,它只是堆疊指標的上下移動,所以堆疊的效能是相當高的。

下面看一下引用型別的存儲,還是先看程式碼:

{

Customer customerA;

customerA=new Customer(); //假設Customer

第一行先宣告了一個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

{

void void!

Dispose(true);

System.GC.SuppressFinalize(this);

// 上面一行程式碼作用是防止"垃圾回收器"呼叫這個類別中的析構方法

// " ~ResourceHolder() "

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

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

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

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

}

protected virtual void Dispose(bool disposing)

{

if (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()方法了。


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn