C#堆和棧
宣告:以下內容從網路整理,非原創,適當待入個人理解.
解釋1、棧是編譯期間就分配好的記憶體空間,因此你的程式碼中必須就棧的大小有明確的定義;堆是程式執行期間動態分配的記憶體空間,你可以根據程式的執行情況確定要分配的堆記憶體的大小
解釋2、
存放在棧中時要管儲存順序,保持著先進後出的原則,他是一片連續的記憶體域,有系統自動分配和維護。
而堆是無序的,他是一片不連續的記憶體域,有使用者自己來控制和釋放,如果使用者自己不釋放的話,當記憶體達到一定的特定值時,通過垃圾回收器(GC)來回收。
引用型別總是存放在堆中。
值型別和指標總是放在它們被宣告的地方。
呼叫方法:系統先將一段編碼(堆的首部地址)放到棧上,緊接著放置方法的引數。然後程式碼執行到方法時,查詢棧中放該堆首部地址的所有引數,並通過堆的首部地址來控制堆。
引用型別:總是放在堆當中。
當我們使用引用型別時,實際上只是在處理該型別的指標。而非引用型別本身,使用值型別的話則是使用其本身。
解釋3.
執行緒堆疊:簡稱棧 Stack
託管堆: 簡稱堆 Heap
使用.Net框架開發程式的時候,我們無需關心記憶體分配問題,因為有GC這個大管家給我們料理一切。如果我們寫出如下兩段程式碼:
public int AddFive(int pValue) { int result; result = pValue + 5; return result; } public class MyInt { public int MyValue; } public MyInt AddFive(int pValue) { MyInt result = new MyInt(); result.MyValue = pValue + 5; return result; }
問題1:你知道程式碼段1在執行的時候,pValue和result在記憶體中是如何存放,生命週期又如何?程式碼段2呢?
要想釋疑以上問題,我們就應該對.Net下的棧(Stack)和託管堆(Heap)(簡稱堆)有個清楚認識,本立而道生。如果你想提高程式效能,理解棧和堆,必須的!
本文就從棧和堆,型別變數展開,對我們寫的程式進行庖丁解牛。
C#程式在CLR上執行的時候,記憶體從邏輯上劃分兩大塊:棧,堆。這倆基本元素組成我們C#程式的執行環境。
一,棧 vs 堆:區別?
棧通常儲存著我們程式碼執行的步驟,如在程式碼段1中 AddFive()方法,int pValue變數,int result變數等等。
而堆上存放的則多是物件,資料等。
我們可以把棧想象成一個接著一個疊放在一起的盒子(越高記憶體地址越低)。當我們使用的時 候,每次從最頂部取走一個盒子,當一個方法(或型別)被呼叫完成的時候,就從棧頂取走(called a Frame,譯註:呼叫幀),接著下一個。
棧記憶體無需我們管理,也不受GC管理。當棧頂元素使用完畢,立馬釋放。而堆則需要GC(Garbage collection:垃圾收集器)清理。
堆則不然,像是一個倉庫,儲存著我們使用的各種物件等資訊,跟棧不同的是他們被呼叫完畢不會立即被清理掉。
如圖1,棧與堆示意圖
二,引用和值型別如何分配?
我們先看一下兩個觀點:
觀點1,引用型別總是被分配在堆上。(正確?)
觀點2,值型別和指標總是分配在被定義的地方,他們不一定被分配到棧上。
上文提及的棧(Stack),在程式執行的時候,每個執行緒(Thread)都會維護一個自己的專屬執行緒堆疊。
當一個方法被呼叫的時候,主執行緒開始在所屬程式集的元資料中,查詢被呼叫方法,然後通過JIT即時編譯並把結果(一般是本地CPU指令)放在棧頂。CPU通過匯流排從棧頂取指令,驅動程式以執行下去。
下面我們以例項來詳談。
還是我們開篇所列的程式碼段1:
public int AddFive(int pValue) { int result; result = pValue + 5; return result; }
當AddFive方法開始執行的時候,方法引數(parameters)則在棧上分配。如圖3:
注意:方法並不在棧中存活,圖示僅供參考。
接著,指令指向AddFive方法內部,如果該方法是第一次執行,首先要進行JIT即時編譯。如圖4:
方法執行完畢,而且方法返回後,如圖6所示:
在方法執行完畢返回後,棧上的區域被清理。如圖7:
現在可以回答第一個問題了 : 很明顯 pValue 與 result 都是被分配在stack上的,而且生命週期為這個函式的生命週期
以上看出,一個值型別變數,一般會分配在棧上。那觀點2中所述又做何理解?“值型別和指標總是分配在被定義的地方,他們不一定被分配到棧上”。
原因就是如果一個值型別被宣告在一個方法體外並且在一個引用型別中,那它就會在堆上進行分配。
還是程式碼段2
public
class
MyInt
{
public
int
MyValue;
}
public
MyInt AddFive(
int
pValue)
{
MyInt result =
new
MyInt();
result.MyValue = pValue + 5;
return
result;
}
剛開是的時候和程式碼段一是一樣的,先找到方法,然後定義引數 pValue
接下來就不一樣了,要定義一個引用型別
1 |
MyInt result =
new
MyInt();
|
new Myint()將會出現在託管堆中,而result被定義在堆疊中,其內容是指向 new MyInt()的地址,如下如所示
AddFive方法執行完畢後 stack將被清理,而heap將會被保留一段時間,這一段時間示情況而定,如果沒有任何引用指向MyInt 垃圾管理器將會在合適的時候(不確定的時間)處理它
這樣就能很好的回答問題一了
值型別巢狀引用型別 : 和程式碼段二的理解方式一致,值型別將會被分配在stack上,二其內部的引用將會在heap上被宣告.
引用型別巢狀值型別:如上圖的郵編堆,都會被宣告在heap上