Java內(nèi)存的故事
Stack堆棧, 和Heap堆
先科普一下計(jì)算機(jī)里內(nèi)存內(nèi)存的結(jié)構(gòu):

每當(dāng)一個(gè)程序被執(zhí)行,系統(tǒng)就要為它開(kāi)啟一個(gè)進(jìn)程,并且為它分配內(nèi)存。從低址區(qū)到高址區(qū),分成幾個(gè)不同的區(qū)域。
低址:存放程序代碼本身。
次低址:存放全局變量,無(wú)論是初始化的還是未初始化的。
中址:就是堆和堆棧的區(qū)域。用來(lái)儲(chǔ)存進(jìn)程運(yùn)行過(guò)程中產(chǎn)生的變量。
最高址:為系統(tǒng)額外預(yù)留的空間。我們無(wú)法操作。
stack(堆棧)
從高址區(qū)往下延展。用來(lái)存儲(chǔ)Scoped Variable(限域變量)。簡(jiǎn)單說(shuō)就是已知存儲(chǔ)空間以及生命周期的變量。為什么stack效率高呢?因?yàn)樽兞看笮〈_定,都是緊挨著儲(chǔ)存的,在堆棧中創(chuàng)建和釋放儲(chǔ)存空間只要一條匯編語(yǔ)言,分別是將棧頂指針向下和向上移動(dòng)。而stack本身又是LIFO(Last in First out)的,所以效率極高。
heap(堆)
從較低地址區(qū)往上移動(dòng)。heap是個(gè)動(dòng)態(tài)內(nèi)存池。從下面的圖我們看的很清楚,heap不像stack那樣是數(shù)據(jù)是連續(xù)的。heap的數(shù)據(jù)是不連續(xù)的,動(dòng)態(tài)隨便亂貼的。創(chuàng)建和釋放效率都不高。

Java對(duì)象存在哪兒?
“引用”存放在”stack”(堆棧)中
Java聲稱一切都是對(duì)象,Java完全采用了動(dòng)態(tài)內(nèi)存分配方式。這是因?yàn)樗袆?chuàng)建的對(duì)象全都繼承自單根基類(lèi)Object,而這個(gè)Object又只能以唯一的方式從heap堆中創(chuàng)建。

對(duì)于 Java 應(yīng)用程序,實(shí)際上包含兩個(gè)池:Java 堆和本機(jī)(非 Java)堆。Java 堆的大小由 JVM 的 Java 堆設(shè)置控制:-Xms 和 -Xmx 分別設(shè)置最小和最大 Java 堆。在按照最大的大小設(shè)置分配了 Java 堆之后,剩下的用戶空間就是本機(jī)堆
本機(jī)堆里包含JVM堆。剩下的就是空閑Native堆。上圖中顯得Java數(shù)據(jù)好像全在heap里,完全不用stack。這是不準(zhǔn)確的,Java用stack!實(shí)際上,Java每個(gè)對(duì)象都有一個(gè)指向他的指針,叫”引用“??梢岳斫鉃镃++的指針。Java不是不用指針,只是泛化他,看下面這個(gè)聲明
String s;
這里s創(chuàng)建的只是”引用”,并不是”對(duì)象”。這時(shí)候還沒(méi)有對(duì)象,只有引用。這時(shí)候如果要求輸出s,系統(tǒng)會(huì)返回錯(cuò)誤。
對(duì)象的引用存放在”stack“(堆棧)中。
“對(duì)象”存放在”heap”(堆)中|
new關(guān)鍵字,負(fù)責(zé)創(chuàng)建對(duì)象。對(duì)象存放在heap(堆)中??聪旅娴睦樱?br>String s = new String("hello");
這時(shí)候s是reference引用,存在stack堆棧里。String(hello)是對(duì)象,存在heap堆中。
當(dāng)然,我們可以用另一種”奇怪”的方式聲明一個(gè)String,
String s = "hello";
這里不用new關(guān)鍵字,只不過(guò)是Java的一個(gè)”特性”,并不是本性。只是說(shuō)Java用了特殊的方法,形式上允許不用new來(lái)創(chuàng)建一個(gè)String對(duì)象,可以直接賦值。但本質(zhì)上,Java內(nèi)部處理以后,這個(gè)”hello”還是以對(duì)象的方式存在heap區(qū)里。
例外
特別強(qiáng)調(diào)八種基本類(lèi)型
因?yàn)樗麄儾粚儆趯?duì)象,不存放在heap堆區(qū),而是直接存在stack堆棧區(qū)。因?yàn)樗麄兊乃加玫拇鎯?chǔ)空間和生命周期都是已知的。
讓我們?cè)僬把鲆幌滤麄儌グ兜拿嫒荨F鋵?shí)下面有九種,因?yàn)榧由狭艘粋€(gè)void空型。

基本類(lèi)型對(duì)應(yīng)的包裝器類(lèi)是在堆中創(chuàng)建的非基本對(duì)象。
一個(gè)Java對(duì)象里有些啥?
Java一切都是對(duì)象的理念很美,但付出的內(nèi)存的代價(jià)也是巨大的。對(duì)象的元數(shù)據(jù),大小相當(dāng)驚人,一般都是他們存放的數(shù)據(jù)本身的好幾倍,根據(jù) JVM 的版本和供應(yīng)的不同,對(duì)象元數(shù)據(jù)的數(shù)量也各有不同,但其中通常包括:
類(lèi):一個(gè)指向類(lèi)信息的指針,描述了對(duì)象類(lèi)型。舉例來(lái)說(shuō),對(duì)于 java.lang.Integer 對(duì)象,這是 java.lang.Integer 類(lèi)的一個(gè)指針。
標(biāo)記:一組標(biāo)記,描述了對(duì)象的狀態(tài),包括對(duì)象的散列碼(如果有),以及對(duì)象的形狀(也就是說(shuō),對(duì)象是否是數(shù)組)。
鎖:對(duì)象的同步信息,也就是說(shuō),對(duì)象目前是否正在同步。
對(duì)象元數(shù)據(jù)后緊跟著對(duì)象數(shù)據(jù)本身,包括對(duì)象實(shí)例中存儲(chǔ)的字段。對(duì)于 java.lang.Integer 對(duì)象,這就是一個(gè) int。 如果您正在運(yùn)行一個(gè) 32 位 JVM,那么在創(chuàng)建 java.lang.Integer 對(duì)象實(shí)例時(shí),對(duì)象的布局可能如下圖所示。也就是說(shuō),為了儲(chǔ)存一個(gè)32位的int數(shù)據(jù),java要占用128位內(nèi)存。



Java的作用域
前面說(shuō)過(guò)了,java把對(duì)象的引用存在stack區(qū),把對(duì)象存在heap區(qū)??聪旅孢@張圖

所有存在stack區(qū)的內(nèi)容,還是遵守花括號(hào){}的作用域,比如基本型i=4,y=2,還有對(duì)象的引用cls1,出了域的終點(diǎn)–花括號(hào),就都消失了,所以他們的作用域和占用空間是已知的。但在heap區(qū)的對(duì)象本身還存在,并沒(méi)有被銷(xiāo)毀。只是我們已經(jīng)找不到他了,因?yàn)橹赶蛩囊胏ls1已經(jīng)擦除了。
這是Java很好的一個(gè)特性,因?yàn)橹灰覀冏⒁鈧鬟f和復(fù)制對(duì)象的引用,在后面的程序中我們一直可以調(diào)用這個(gè)對(duì)象。因?yàn)橹灰幸粋€(gè)引用能讓我們找到他,他一直在那兒。
另一個(gè)好處(對(duì)程序員),但也可以說(shuō)是壞處(對(duì)系統(tǒng)),就是一旦一個(gè)對(duì)象完全失去引用,我們不必像C++這樣手動(dòng)用delete()釋放heap區(qū)的對(duì)象。我們可以完全不去管它。但我們不管他,意味著必須有別人去管它,這就是JVM里的垃圾回收器(Garbage Collection)。但因?yàn)樘幚淼膶?duì)象都是heap區(qū)里的家伙,所以開(kāi)銷(xiāo)要大很多,對(duì)系統(tǒng)負(fù)擔(dān)也很大。