JVM調優總結(一)-- 一些概念

zhoubangding發表於2017-02-14

轉自:http://pengjiaheng.iteye.com/blog/518623

資料型別

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

基本型別包括:byte,short,int,long,char,float,double,boolean,returnAddress

引用型別包括:類型別介面型別陣列

堆與棧

    堆和棧是程式執行的關鍵,很有必要把他們的關係說清楚。


棧是執行時的單位,而堆是儲存的單位

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

    在Java中一個執行緒就會相應有一個執行緒棧與之對應,這點很容易理解,因為不同的執行緒執行邏輯有所不同,因此需要一個獨立的執行緒棧。而堆則是所有執行緒共享的。棧因為是執行單位,因此裡面儲存的資訊都是跟當前執行緒(或程式)相關資訊的。包括區域性變數、程式執行狀態、方法返回值等等;而堆只負責儲存物件資訊。

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

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

    第二,堆與棧的分離,使得堆中的內容可以被多個棧共享(也可以理解為多個執行緒訪問同一個物件)。這種共享的收益是很多的。一方面這種共享提供了一種有效的資料互動方式(如:共享記憶體),另一方面,堆中的共享常量和快取可以被所有棧訪問,節省了空間。

    第三,棧因為執行時的需要,比如儲存系統執行的上下文,需要進行地址段的劃分。由於棧只能向上增長,因此就會限制住棧儲存內容的能力。而堆不同,堆中的物件是可以根據需要動態增長的,因此棧和堆的拆分,使得動態增長成為可能,相應棧中只需記錄堆中的一個地址即可。

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

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

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

    堆中存什麼?棧中存什麼

    堆中存的是物件。棧中存的是基本資料型別堆中物件的引用。一個物件的大小是不可估計的,或者說是可以動態變化的,但是在棧中,一個物件只對應了一個4btye的引用(堆疊分離的好處:))。

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

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

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

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

         2. 程式執行永遠都是在棧中進行的,因而引數傳遞時,只存在傳遞基本型別和物件引用的問題。不會直接傳物件本身。

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

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

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

 

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

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


相關文章