盡管在Java我們并不需要擔(dān)心內(nèi)存管理和垃圾回收(GarbageCollection),但是我們還是應(yīng)該了解它們,以優(yōu)化我們的應(yīng)用程序。同時,還需要具備一些基礎(chǔ)的內(nèi)存管理工作機(jī)制的知識,這樣能夠有助于解釋我們?nèi)粘3绦蚓帉懼械淖兞康男袨椤T诒疚闹形覍⒅v解棧和堆的基本知識,變量類型以及為什么一些變量能夠按照它們自己的方式工作。
當(dāng)我們的代碼執(zhí)行時,內(nèi)存中有兩個地方用來存儲這些代碼。假如你不曾了解,那就讓我來給你介紹棧(Stack)和堆(Heap)。棧和堆都用來幫助我們運行代碼的,它們駐留在機(jī)器內(nèi)存中,且包含所有代碼執(zhí)行所需要的信息。
棧vs堆:有什么不同?
棧負(fù)責(zé)保存我們的代碼執(zhí)行(或調(diào)用)路徑,而堆則負(fù)責(zé)保存對象(或者說數(shù)據(jù),接下來將談到很多關(guān)于堆的問題)的路徑。
可以將棧想象成一堆從頂向下堆疊的盒子。當(dāng)每調(diào)用一次方法時,我們將應(yīng)用程序中所要發(fā)生的事情記錄在棧頂?shù)囊粋€盒子中,而我們每次只能夠使用棧頂?shù)哪莻€盒子。當(dāng)我們棧頂?shù)暮凶颖皇褂猛曛螅蛘哒f方法執(zhí)行完畢之后,我們將拋開這個盒子然后繼續(xù)使用棧頂上的新盒子。堆的工作原理比較相似,但大多數(shù)時候堆用作保存信息而非保存執(zhí)行路徑,因此堆能夠在任意時間被訪問。與棧相比堆沒有任何訪問限制,堆就像床上的舊衣服,我們并沒有花時間去整理,那是因為可以隨時找到一件我們需要的衣服,而棧就像儲物柜里堆疊的鞋盒,我們只能從最頂層的盒子開始取,直到發(fā)現(xiàn)那只合適的。

以上圖片并不是內(nèi)存中真實的表現(xiàn)形式,但能夠幫助我們區(qū)分棧和堆。
棧是自行維護(hù)的,也就是說內(nèi)存自動維護(hù)棧,當(dāng)棧頂?shù)暮凶硬辉俦皇褂?,它將被拋出。相反的,堆需要考慮垃圾回收,垃圾回收用于保持堆的整潔性,沒有人愿意看到周圍都是贓衣服,那簡直太臭了!
棧和堆里有些什么?
當(dāng)我們的代碼執(zhí)行的時候,棧和堆中主要放置了四種類型的數(shù)據(jù):值類型(Value Type),引用類型(Reference Type),指針(Pointer),指令(Instruction)。
1.基本類型:
在java中,所有被聲明為以下類型的事物被稱為基本類型:
byte short int long float double char boolean
2.引用類型:
所有的被聲明為以下類型的事物被稱為引用類型:
class interface delegate object string
3.指針:
在內(nèi)存管理方案中放置的第三種類型是類型引用,引用通常就是一個指針。指針(或引用)是不同于引用類型的,是因為當(dāng)我們說某個事物是一個引用類型時就意味著我們是通過指針來訪問它的。指針是一塊內(nèi)存空間,而它指向另一個內(nèi)存空間。就像棧和堆一樣,指針也同樣要占用內(nèi)存空間,但它的值是一個內(nèi)存地址或者為空。

4.指令:
在后面的文章中你會看到指令是如何工作的...
如何決定放哪兒?
這里有一條黃金規(guī)則:
- 引用類型總是放在堆中。
-
值類型和指針總是放在它們被聲明的地方。(這條稍微復(fù)雜點,需要知道棧是如何工作的,然后才能斷定是在哪兒被聲明的。)
就像我們先前提到的,棧是負(fù)責(zé)保存我們的代碼執(zhí)行(或調(diào)用)時的路徑。當(dāng)我們的代碼開始調(diào)用一個方法時,將放置一段編碼指令(在方法中)到棧上,緊接著放置方法的參數(shù),然后代碼執(zhí)行到方法中的被“壓?!敝翖m?shù)淖兞课恢?。通過以下例子很容易理解...
下面是一個方法(Method):
public int AddFive(int pValue) { int result; result = pValue + 5; return result; }
現(xiàn)在就來看看在棧頂發(fā)生了些什么,記住我們所觀察的棧頂下實際已經(jīng)壓入了許多別的內(nèi)容。
首先方法(只包含需要執(zhí)行的邏輯字節(jié),即執(zhí)行該方法的指令,而非方法體內(nèi)的數(shù)據(jù))入棧,緊接著是方法的參數(shù)入棧。(我們將在后面討論更多的參數(shù)傳遞)
接著,控制(即執(zhí)行方法的線程)被傳遞到堆棧中AddFive()的指令上,
當(dāng)方法執(zhí)行時,我們需要在棧上為“result”變量分配一些內(nèi)存,
Themethod finishes execution and our result is returned.方法執(zhí)行完成,然后方法的結(jié)果被返回。
通過將棧指針指向AddFive()方法曾使用的可用的內(nèi)存地址,所有在棧上的該方法所使用內(nèi)存都被清空,且程序?qū)⒆詣踊氐綏I献畛醯姆椒ㄕ{(diào)用的位置(在本例中不會看到)。
在這個例子中,我們的"result"變量是被放置在棧上的,事實上,當(dāng)值類型數(shù)據(jù)在方法體中被聲明時,它們都是被放置在棧上的。
值類型數(shù)據(jù)有時也被放置在堆上。記住這條規(guī)則--值類型總是放在它們被聲明的地方。好的,如果一個值類型數(shù)據(jù)在方法體外被聲明,且存在于一個引用類型中,那么它將被堆中的引用類型所取代。
來看另一個例子:
假如我們有這樣一個MyInt類(它是引用類型因為它是一個類類型):
public class MyInt { publicint MyValue; }
然后執(zhí)行下面的方法:
public MyInt AddFive(int pValue) { MyInt result = new MyInt(); result.MyValue = pValue + 5; return result; }
就像前面提到的,方法及方法的參數(shù)被放置到棧上,接下來,控制被傳遞到堆棧中AddFive()的指令上。
接著會出現(xiàn)一些有趣的現(xiàn)象...
因為"MyInt"是一個引用類型,它將被放置在堆上,同時在棧上生成一個指向這個堆的指針引用。
在AddFive()方法被執(zhí)行之后,我們將清空...
我們將剩下孤獨的MyInt對象在堆中(棧中將不會存在任何指向MyInt對象的指針!)
這就是垃圾回收器(后簡稱GC)起作用的地方。當(dāng)我們的程序達(dá)到了一個特定的內(nèi)存閥值,我們需要更多的堆空間的時候,GC開始起作用。GC將停止所有正在運行的線程,找出在堆中存在的所有不再被主程序訪問的對象,并刪除它們。然后GC會重新組織堆中所有剩下的對象來節(jié)省空間,并調(diào)整棧和堆中所有與這些對象相關(guān)的指針。你肯定會想到這個過程非常耗費性能,所以這時你就會知道為什么我們需要如此重視棧和堆里有些什么,特別是在需要編寫高性能的代碼時。
Ok...這太棒了, 當(dāng)它是如何影響我的?
Goodquestion.
當(dāng)我們使用引用類型時,我們實際是在處理該類型的指針,而非該類型本身。當(dāng)我們使用值類型時,我們是在使用值類型本身。聽起來很迷糊吧?
同樣,例子是最好的描述。
假如我們執(zhí)行以下的方法:
public int ReturnValue() { int x = new int(); x = 3; int y = new int(); y = x; y = 4; return x; }
我們將得到值3,很簡單,對吧?
假如我們首先使用MyInt類
public class MyInt { public int MyValue; }
接著執(zhí)行以下的方法:
public int ReturnValue2() { MyInt x = new MyInt(); x.MyValue = 3; MyInt y = new MyInt(); y =x; y.MyValue =4; return x.MyValue; }
我們將得到什么?... 4!
為什么?... x.MyValue怎么會變成4了呢?... 看看我們所做的然后就知道是怎么回事了:
在第一例子中,一切都像計劃的那樣進(jìn)行著:
public int ReturnValue() { int x = 3; int y = x; y = 4; return x; }

在第二個例子中,我們沒有得到"3"是因為變量"x"和"y"都同時指向了堆中相同的對象。
public intReturnValue2() {MyInt x; x.MyValue = 3; MyInt y; y =x; y.MyValue = 4; return x.MyValue; }

希望以上內(nèi)容能夠使你對java中的基本類型和引用類型的基本區(qū)別有一個更好的認(rèn)識,并且對指針及指針是何時被使用的有一定的基本了解。







