天天看點

JVM基礎:深入學習JVM堆與JVM棧-堆與棧的經典問題

JVM棧解決程式的運作問題,即程式如何執行,或者說如何處理資料;

JVM堆解決的是資料存儲的問題,即資料怎麼放、放在哪兒,另外JVM堆中存的是對象。

JVM棧中存的是基本資料類型和JVM堆中對象的引用。

JVM基礎概念:JVM堆與JVM棧

資料類型

Java虛拟機中,資料類型可以分為兩類:基本類型和引用類型。基本類型的變量儲存原始值,即:他代表的值就是數值本身;而引用類型的變量儲存引用值。“引用值”代表了某個對象的引用,而不是對象本身,對象本身存放在這個引用值所表示的位址的位置。

基本類型包括:byte,short,int,long,char,float,double,Boolean,returnAddress

引用類型包括:類類型,接口類型和數組。

JVM堆與JVM棧

JVM堆和JVM棧是程式運作的關鍵,很有必要把他們的關系說清楚。

JVM堆和JVM棧

JVM棧是運作時的機關,而JVM堆是存儲的機關。

JVM棧解決程式的運作問題,即程式如何執行,或者說如何處理資料;JVM堆解決的是資料存儲的問題,即資料怎麼放、放在哪兒。

在Java中一個線程就會相應有一個線程JVM棧與之對應,這點很容易了解,因為不同的線程執行邏輯有所不同,是以需要一個獨立的線程JVM棧。而JVM堆則是所有線程共享的。JVM棧因為是運作機關,是以裡面存儲的資訊都是跟目前線程(或程式)相關資訊的。包括局部變量、程式運作狀态、方法傳回值等等;而JVM堆隻負責存儲對象資訊。

為什麼要把JVM堆和JVM棧區分出來呢?JVM棧中不是也可以存儲資料嗎?

第一,從軟體設計的角度看,JVM棧代表了處理邏輯,而JVM堆代表了資料。這樣分開,使得處理邏輯更為清晰。分而治之的思想。這種隔離、子產品化的思想在軟體設計的方方面面都有展現。

第二,JVM堆與JVM棧的分離,使得JVM堆中的内容可以被多個JVM棧共享(也可以了解為多個線程通路同一個對象)。這種共享的收益是很多的。一方面這種共享提供了一種有效的資料互動方式(如:共享記憶體),另一方面,JVM堆中的共享常量和緩存可以被所有JVM棧通路,節省了空間。

第三,JVM棧因為運作時的需要,比如儲存系統運作的上下文,需要進行位址段的劃分。由于JVM棧隻能向上增長,是以就會限制住JVM棧存儲内容的能力。而JVM堆不同,JVM堆中的對象是可以根據需要動态增長的,是以JVM棧和JVM堆的拆分,使得動态增長成為可能,相應JVM棧中隻需記錄JVM堆中的一個位址即可。

第四,面向對象就是JVM堆和JVM棧的完美結合。其實,面向對象方式的程式與以前結構化的程式在執行上沒有任何差別。但是,面向對象的引入,使得對待問題的思考方式發生了改變,而更接近于自然方式的思考。當我們把對象拆開,你會發現,對象的屬性其實就是資料,存放在JVM堆中;而對象的行為(方法),就是運作邏輯,放在JVM棧中。我們在編寫對象的時候,其實即編寫了資料結構,也編寫的處理資料的邏輯。不得不承認,面向對象的設計,确實很美。

在Java中,Main函數就是JVM棧的起始點,也是程式的起始點。

程式要運作總是有一個起點的。同C語言一樣,java中的Main就是那個起點。無論什麼java程式,找到main就找到了程式執行的入口:)

JVM堆中存什麼?JVM棧中存什麼?

JVM堆中存的是對象。JVM棧中存的是基本資料類型和JVM堆中對象的引用。一個對象的大小是不可估計的,或者說是可以動态變化的,但是在JVM棧中,一個對象隻對應了一個4btye的引用(JVM堆JVM棧分離的好處:))。

為什麼不把基本類型放JVM堆中呢?因為其占用的空間一般是1~8個位元組——需要空間比較少,而且因為是基本類型,是以不會出現動态增長的情況——長度固定,是以JVM棧中存儲就夠了,如果把他存在JVM堆中是沒有什麼意義的(還會浪費空間,後面說明)。可以這麼說,基本類型和對象的引用都是存放在JVM棧中,而且都是幾個位元組的一個數,是以在程式運作時,他們的處理方式是統一的。但是基本類型、對象引用和對象本身就有所差別了,因為一個是JVM棧中的資料一個是JVM堆中的資料。最常見的一個問題就是,Java中參數傳遞時的問題。

Java中的參數傳遞時傳值呢?還是傳引用?

要說明這個問題,先要明确兩點:

1.不要試圖與C進行類比,Java中沒有指針的概念

2.程式運作永遠都是在JVM棧中進行的,因而參數傳遞時,隻存在傳遞基本類型和對象引用的問題。不會直接傳對象本身。

明确以上兩點後。Java在方法調用傳遞參數時,因為沒有指針,是以它都是進行傳值調用(這點可以參考C的傳值調用)。是以,很多書裡面都說Java是進行傳值調用,這點沒有問題,而且也簡化的C中複雜性。

但是傳引用的錯覺是如何造成的呢?在運作JVM棧中,基本類型和引用的處理是一樣的,都是傳值,是以,如果是傳引用的方法調用,也同時可以了解為“傳引用值”的傳值調用,即引用的處理跟基本類型是完全一樣的。但是當進入被調用方法時,被傳遞的這個引用的值,被程式解釋(或者查找)到JVM堆中的對象,這個時候才對應到真正的對象。如果此時進行修改,修改的是引用對應的對象,而不是引用本身,即:修改的是JVM堆中的資料。是以這個修改是可以保持的了。

對象,從某種意義上說,是由基本類型組成的。可以把一個對象看作為一棵樹,對象的屬性如果還是對象,則還是一顆樹(即非葉子節點),基本類型則為樹的葉子節點。程式參數傳遞時,被傳遞的值本身都是不能進行修改的,但是,如果這個值是一個非葉子節點(即一個對象引用),則可以修改這個節點下面的所有内容。

JVM堆和JVM棧中,JVM棧是程式運作最根本的東西。程式運作可以沒有JVM堆,但是不能沒有JVM棧。而JVM堆是為JVM棧進行資料存儲服務,說白了JVM堆就是一塊共享的記憶體。不過,正是因為JVM堆和JVM棧的分離的思想,才使得Java的垃圾回收成為可能。

Java中,JVM棧的大小通過-Xss來設定,當JVM棧中存儲資料比較多時,需要适當調大這個值,否則會出現java.lang.StackOverflowError異常。常見的出現這個異常的是無法傳回的遞歸,因為此時JVM棧中儲存的資訊都是方法傳回的記錄點。