.NET framework使我們不需要刻意關心內存管理和垃圾回收(GC),但是當我們需要優化應用的性能的時,我們就需要對他們有所了解。理解內存解能幫助我們知道我們所寫編碼中變量的行為,在這篇文章中我將描述 堆和棧的基本知識, 變量和變量的工作原理。
當執行程序時 .NET framework有倆個地方來存放數據,他們就是堆 (Heap)和棧 (Stack),他們位於我們機器的內存里保存我們程序運行過程中的數據。
堆 (Heap
) Vs 棧 (Stack)
不同之處?
棧 (Stack) 負責記錄線程的運行運行到哪里(或者什么正在被調用)
堆 (Heap)負責保存對象,數據...
我們可以把棧(Stack)想象為從上到下疊放在一起的盒子,我每次調用一個方法時就相當於在這些盒上放一個新的盒子,我們通過記錄這個新的盒子來表示程序正在干什么 (或者說運行到哪里), 我們只能使用最頂端的盒子,當最頂端的盒子被使用完成后(被執行的方法執行完畢),我們就把這個盒子拿掉,接着
處理
緊挨着被拿掉盒子下面的第一個盒子(也即當前最頂端的盒子),堆也是很相似除了他的目的是用來保存信息(大部分時間它不記錄執行狀態)所以堆上的任何東西都是可以被隨時訪問的,它不像棧那樣對訪問其上面的東西有限制,堆就像是一堆我們洗干凈放在床上還沒有整理的衣服,我們可以很快找到我們要穿的那件衣服,棧就像是壁櫥里面疊在一起的鞋盒,如果我們想拿第二個盒子那我們就必須先把第一個拿出來。
上面的圖片不能真正的反應堆和棧在內存的狀態,只是用來幫助我們區分和理解它們。
棧是自我管理的,也就是說他是自己管理自己的內存,當第一層的盒子不需要再被使用了,那么這個盒子就會被拿掉,堆卻相反,他必須考慮垃圾回收(GC),使堆保持干凈 (沒有人希望床上都是臟兮兮的衣服,它讓人作惡)
堆和棧上都是什么?
有四種主要的類型將會被放到堆和棧上當代碼運行時:值類型,引用類型,指針,和指令。
值類型:
在C# 中所有被下面類型
聲明的變量都是值類型的 (因為這些類型來自 System.ValueType):
- bool
- byte
- char
- decimal
- double
- enum
- float
- int
- long
- sbyte
- short
- struct
- uint
- ulong
- ushort
引用類型:
所有被下面類型聲明的類型 (繼承System.Object... 還有那些它本身就是System.Object 的對象)
- class
- interface
- delegate
- object
- string
指針:
第三種類型是作為一個類型的引用引入到我們的內存管理的模式中,一個引用通常也被稱作指針,我們並沒有明確的使用指針,他們由CLR管理。
指針和引用類型是不同的,我們說一個東西是引用類型也就是說我們可以通過指針來訪問它,一個指針就是一塊內存空間它指向內存中的另一個地方,
指針像其他類型一樣可以被放到堆和棧中並占用內存空間,他的值可以使一個內存地址或者null.
指令:
我們將在后續的文章中介紹它。
那么它們是怎么樣被分配到堆和棧上面的?
好的,這是最后一個比較有意思的事情。
這里有倆個黃金法則:
1,引用類型肯定是放在堆上, 簡單吧?:)
2,值類型和指針類型由他們在哪里聲明決定,這個有點復雜,我們需要理解棧是怎么工作的,然后才能指出他們是在哪里聲明的。
棧,如我們開始所說,它負責記錄每個線程執行我們的代碼到了那個位置(或者什么正在被執行), 你可以把它理解為線程的“狀態”,並且每個線程有自己的棧
當我們的代碼需要去執行一個方法時,線程就會去執行已經被 JIT編譯好的並且位於方法表中的一系列指令,它同時也會把方法的參數分配到棧棧頂上,
然后他就會執行位於棧頂的代碼用屬於這個方法的變量,我們可以通過一個例子來理解它...
我們來看下面的方法。
1
public
int
AddFive(
int
pValue)
2 {
3 int result;
4 result = pValue + 5 ;
5 return result;
6 }
2 {
3 int result;
4 result = pValue + 5 ;
5 return result;
6 }
下面就是在棧頂發生的事情,請注意當我們在關注棧頂的時候其他的一些相關的東西已經在放到棧中
一旦我們開始執行方法,棧上就會分配該方法的參數(我們將會在后面討論怎么傳遞參數)
注意
,方法不是居於棧中,它只供我們描述參考

接下來,控制開始執行AddFive() 位於類型方法表中的
指令
,如果是第一次執行這個方法,JIT就會編譯這些指令。

隨方法的執行,我們需要在棧分配內存給“result”變量。

方法執行結束,我們的結果返回。

然后棧向下移動指針到這個方法下面的方法,釋放分配給這個方法的所有資源。
在這個示例中, 我們的 "result" 變量是分配在棧上面的,實際上,方法內部的所有值類型變量都是分配在棧上的。
現在 值類型有時也會被分配到堆上面,記住,值類型都是在哪里聲明就會被分配到哪里。 如果一個值類型在方法體外面定義,但是卻在一個引用類型內部,它將會被分配到引用類型內的堆上面。
我們來看下面一個例子。
我們有下面一個 MyInt 類 (一個引用類型,因為它是一個類)
1
public
class
MyInt
2 {
3 public int MyValue;
4 }
2 {
3 public int MyValue;
4 }
然后下面的方法被執行:
1
public
MyInt AddFive(
int
pValue)
2 {
3 MyInt result = new MyInt();
4 result.MyValue = pValue + 5 ;
5 return result;
6 }
2 {
3 MyInt result = new MyInt();
4 result.MyValue = pValue + 5 ;
5 return result;
6 }
如剛才一樣,線程開始執行這個方法,方法的參數也被分配到線程的棧上

現在事情變得有意思了...
因為MyInt 是一個引用類型,它被分配到堆上然后被一個棧上面的指針引用
當 AddFive() 被執行完后(就像我們的第一個例子一樣)我們開始清理...
然后只剩下一個孤獨的MyInt在堆上面(棧上沒有任何指針指向它(MyInt))
現在垃圾回收(GC)就出場了,一旦我們的程序運行到一個臨界點並且我們需要更多的堆空間,我們的GC就開始運作,它會停止所有的線程,清理所有堆上面的沒有被程序使用的對象,
然后刪除它們,重新組織所有剩下的對象來騰出空間並調整所有對象在堆和棧上的指針,顯然這對性能有很大的影響,所以我們知道理解堆和棧是什么對我們寫出高性能的代碼是
多么重要。
那它是怎樣影響我們編程的啦?
當我們使用引用類型時,我們要處理指向這些類型的指針,而不是這些類型。
當我們使用值類型,我們真正的在使用它們。太簡單了,不是嗎?
我們再用例子來解釋下吧,
如果我們執行下面的方法:
1
public
int
ReturnValue()
2 {
3 int x = new int ();
4 x = 3 ;
5 int y = new int ();
6 y = x;
7 y = 4 ;
8 return x;
9 }
2 {
3 int x = new int ();
4 x = 3 ;
5 int y = new int ();
6 y = x;
7 y = 4 ;
8 return x;
9 }
我們得到值類型 3,太簡單了。 但是如果我們使用先前的MyInt 類啦?
1
public
int
ReturnValue2()
2 {
3 MyInt x = new MyInt();
4 x.MyValue = 3 ;
5 MyInt y = new MyInt();
6 y = x;
7 y.MyValue = 4 ;
8 return x.MyValue;
9 }
2 {
3 MyInt x = new MyInt();
4 x.MyValue = 3 ;
5 MyInt y = new MyInt();
6 y = x;
7 y.MyValue = 4 ;
8 return x.MyValue;
9 }
那我們的結果是? 4!
這是為什么啦? x.MyValue是怎樣變成4的?
在上面第一個例子中它的結果是按我們預想的執行的
1
public
int
ReturnValue()
2 {
3 int x = 3 ;
4 int y = x;
5 y = 4 ;
6 return x;
7 }
2 {
3 int x = 3 ;
4 int y = x;
5 y = 4 ;
6 return x;
7 }

但是在下面一個例子中,我們沒有獲得 “3” 是因為 變量 "x" 和 "y" 都是指向堆上的同一個變量
1
public
int
ReturnValue2()
2 {
3 MyInt x;
4 x.MyValue = 3 ;
5 MyInt y;
6 y = x;
7 y.MyValue = 4 ;
8 return x.MyValue;
9 }
2 {
3 MyInt x;
4 x.MyValue = 3 ;
5 MyInt y;
6 y = x;
7 y.MyValue = 4 ;
8 return x.MyValue;
9 }
希望這寫能讓你對值類型和引用類型有一個更好的理解,知道什么是指針和它們是什么時候使用的,在接下來的文章中我將會深入到內存管理和方法的參數。
原文鏈接:http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory01122006130034PM/csharp_memory.aspx
