盡管在Java我們并不需要擔(dān)心內(nèi)存管理和垃圾回收(GarbageCollection)概漱,但是我們還是應(yīng)該了解它們丑慎,以優(yōu)化我們的應(yīng)用程序。同時(shí)犀概,還需要具備一些基礎(chǔ)的內(nèi)存管理工作機(jī)制的知識立哑,這樣能夠有助于解釋我們?nèi)粘3绦蚓帉懼械淖兞康男袨椤T诒疚闹形覍⒅v解棧和堆的基本知識姻灶,變量類型以及為什么一些變量能夠按照它們自己的方式工作铛绰。
當(dāng)我們的代碼執(zhí)行時(shí),內(nèi)存中有兩個(gè)地方用來存儲這些代碼产喉。假如你不曾了解捂掰,那就讓我來給你介紹棧(Stack)和堆(Heap)敢会。棧和堆都用來幫助我們運(yùn)行代碼的,它們駐留在機(jī)器內(nèi)存中这嚣,且包含所有代碼執(zhí)行所需要的信息鸥昏。
棧vs堆:有什么不同?
棧負(fù)責(zé)保存我們的代碼執(zhí)行(或調(diào)用)路徑,而堆則負(fù)責(zé)保存對象(或者說數(shù)據(jù)姐帚,接下來將談到很多關(guān)于堆的問題)的路徑吏垮。
可以將棧想象成一堆從頂向下堆疊的盒子。當(dāng)每調(diào)用一次方法時(shí)罐旗,我們將應(yīng)用程序中所要發(fā)生的事情記錄在棧頂?shù)囊粋€(gè)盒子中,而我們每次只能夠使用棧頂?shù)哪莻€(gè)盒子膳汪。當(dāng)我們棧頂?shù)暮凶颖皇褂猛曛螅蛘哒f方法執(zhí)行完畢之后九秀,我們將拋開這個(gè)盒子然后繼續(xù)使用棧頂上的新盒子遗嗽。堆的工作原理比較相似,但大多數(shù)時(shí)候堆用作保存信息而非保存執(zhí)行路徑鼓蜒,因此堆能夠在任意時(shí)間被訪問痹换。與棧相比堆沒有任何訪問限制,堆就像床上的舊衣服都弹,我們并沒有花時(shí)間去整理娇豫,那是因?yàn)榭梢噪S時(shí)找到一件我們需要的衣服,而棧就像儲物柜里堆疊的鞋盒缔杉,我們只能從最頂層的盒子開始取锤躁,直到發(fā)現(xiàn)那只合適的。
以上圖片并不是內(nèi)存中真實(shí)的表現(xiàn)形式或详,但能夠幫助我們區(qū)分棧和堆系羞。
棧是自行維護(hù)的,也就是說內(nèi)存自動維護(hù)棧霸琴,當(dāng)棧頂?shù)暮凶硬辉俦皇褂媒氛瘢鼘⒈粧伋觥O喾吹奈喑耍研枰紤]垃圾回收澎迎,垃圾回收用于保持堆的整潔性,沒有人愿意看到周圍都是贓衣服选调,那簡直太臭了夹供!
棧和堆里有些什么?
當(dāng)我們的代碼執(zhí)行的時(shí)候仁堪,棧和堆中主要放置了四種類型的數(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)存管理方案中放置的第三種類型是類型引用,引用通常就是一個(gè)指針匪凉。指針(或引用)是不同于引用類型的枪眉,是因?yàn)楫?dāng)我們說某個(gè)事物是一個(gè)引用類型時(shí)就意味著我們是通過指針來訪問它的。指針是一塊內(nèi)存空間再层,而它指向另一個(gè)內(nèi)存空間贸铜。就像棧和堆一樣,指針也同樣要占用內(nèi)存空間树绩,但它的值是一個(gè)內(nèi)存地址或者為空萨脑。
4.指令:
在后面的文章中你會看到指令是如何工作的...
如何決定放哪兒?
這里有一條黃金規(guī)則:
- 引用類型總是放在堆中隐轩。
-
值類型和指針總是放在它們被聲明的地方饺饭。(這條稍微復(fù)雜點(diǎn),需要知道棧是如何工作的职车,然后才能斷定是在哪兒被聲明的瘫俊。)
就像我們先前提到的,棧是負(fù)責(zé)保存我們的代碼執(zhí)行(或調(diào)用)時(shí)的路徑悴灵。當(dāng)我們的代碼開始調(diào)用一個(gè)方法時(shí),將放置一段編碼指令(在方法中)到棧上,緊接著放置方法的參數(shù)扛芽,然后代碼執(zhí)行到方法中的被“壓棧”至棧頂?shù)淖兞课恢没鳌Mㄟ^以下例子很容易理解...
下面是一個(gè)方法(Method):
public int AddFive(int pValue) { int result; result = pValue + 5; return result; }
現(xiàn)在就來看看在棧頂發(fā)生了些什么川尖,記住我們所觀察的棧頂下實(shí)際已經(jīng)壓入了許多別的內(nèi)容。
首先方法(只包含需要執(zhí)行的邏輯字節(jié)茫孔,即執(zhí)行該方法的指令叮喳,而非方法體內(nèi)的數(shù)據(jù))入棧,緊接著是方法的參數(shù)入棧缰贝。(我們將在后面討論更多的參數(shù)傳遞)
接著馍悟,控制(即執(zhí)行方法的線程)被傳遞到堆棧中AddFive()的指令上,
當(dāng)方法執(zhí)行時(shí)剩晴,我們需要在棧上為“result”變量分配一些內(nèi)存锣咒,
Themethod finishes execution and our result is returned.方法執(zhí)行完成,然后方法的結(jié)果被返回赞弥。
通過將棧指針指向AddFive()方法曾使用的可用的內(nèi)存地址毅整,所有在棧上的該方法所使用內(nèi)存都被清空,且程序?qū)⒆詣踊氐綏I献畛醯姆椒ㄕ{(diào)用的位置(在本例中不會看到)绽左。
在這個(gè)例子中悼嫉,我們的"result"變量是被放置在棧上的,事實(shí)上妇菱,當(dāng)值類型數(shù)據(jù)在方法體中被聲明時(shí)承粤,它們都是被放置在棧上的暴区。
值類型數(shù)據(jù)有時(shí)也被放置在堆上。記住這條規(guī)則--值類型總是放在它們被聲明的地方辛臊。好的仙粱,如果一個(gè)值類型數(shù)據(jù)在方法體外被聲明,且存在于一個(gè)引用類型中彻舰,那么它將被堆中的引用類型所取代伐割。
來看另一個(gè)例子:
假如我們有這樣一個(gè)MyInt類(它是引用類型因?yàn)樗且粋€(gè)類類型):
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)象...
因?yàn)?MyInt"是一個(gè)引用類型,它將被放置在堆上,同時(shí)在棧上生成一個(gè)指向這個(gè)堆的指針引用尚胞。
在AddFive()方法被執(zhí)行之后硬霍,我們將清空...
我們將剩下孤獨(dú)的MyInt對象在堆中(棧中將不會存在任何指向MyInt對象的指針!)
這就是垃圾回收器(后簡稱GC)起作用的地方。當(dāng)我們的程序達(dá)到了一個(gè)特定的內(nèi)存閥值笼裳,我們需要更多的堆空間的時(shí)候唯卖,GC開始起作用。GC將停止所有正在運(yùn)行的線程躬柬,找出在堆中存在的所有不再被主程序訪問的對象拜轨,并刪除它們。然后GC會重新組織堆中所有剩下的對象來節(jié)省空間允青,并調(diào)整棧和堆中所有與這些對象相關(guān)的指針橄碾。你肯定會想到這個(gè)過程非常耗費(fèi)性能,所以這時(shí)你就會知道為什么我們需要如此重視棧和堆里有些什么颠锉,特別是在需要編寫高性能的代碼時(shí)法牲。
Ok...這太棒了, 當(dāng)它是如何影響我的?
Goodquestion.
當(dāng)我們使用引用類型時(shí)木柬,我們實(shí)際是在處理該類型的指針皆串,而非該類型本身。當(dāng)我們使用值類型時(shí)眉枕,我們是在使用值類型本身恶复。聽起來很迷糊吧?
同樣速挑,例子是最好的描述谤牡。
假如我們執(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ì)劃的那樣進(jìn)行著:
public int ReturnValue() { int x = 3; int y = x; y = 4; return x; }
在第二個(gè)例子中,我們沒有得到"3"是因?yàn)樽兞?x"和"y"都同時(shí)指向了堆中相同的對象胚泌。
public intReturnValue2() {MyInt x; x.MyValue = 3; MyInt y; y =x; y.MyValue = 4; return x.MyValue; }
希望以上內(nèi)容能夠使你對java中的基本類型和引用類型的基本區(qū)別有一個(gè)更好的認(rèn)識省咨,并且對指針及指針是何時(shí)被使用的有一定的基本了解。