1. 程式人生 > >Java 堆和棧

Java 堆和棧

Java執行程式包含:

  1. 棧Stack:

    儲存區域性變數的值,包括:
    

    1- 儲存基本資料型別的值 2-儲存類的例項,即堆區物件的引用 3-儲存載入方法時的禎

  2. 堆Heap

    用來儲存動態產生的資料
    

    1-比如new 出來的物件,注意創建出來的物件只包含各自的成員變數 不包括成員方法。因為同一個類的物件擁有各自的成員變數,儲存在各自的堆中, 但是他們共享該類的方法,並不是每建立一個物件就把成員方法複製一次。

舉例: Person p =new Person() 1- 上面產生了兩個東西,一個時變數p ,存放在棧中 2- 產生了Person物件,存放在堆中 物件

  1. 暫存器 JVM內部虛擬暫存器,存取速度非常快,程式不可控制。

方法區:Method Area

  1. 常量池 JVM為每個已載入的型別維護一個常量池,常量池就是這個型別用到的常量的一個有序集合。包括直接常量(基本型別,String)和對其他型別、方法、欄位的符號引用(1)。池中的資料和陣列一樣通過索引訪問。由於常量池包含了一個型別所有的對其他型別、方法、欄位的符號引用,所以常量池在Java的動態連結中起了核心作用。常量池存在於堆中。

  2. 程式碼段 用來存放從硬碟上讀取的源程式程式碼。

  3. 資料段 用來存放static定義的靜態成員。 記憶體圖

總結 1.一個Java檔案,只要有main入口方法,我們就認為這是一個Java程式,可以單獨編譯執行。

2.無論是普通型別的變數還是引用型別的變數(俗稱例項),都可以作為區域性變數,他們都可以出現在棧中。只不過普通型別的變數在棧中直接儲存它所對應的值,而引用型別的變數儲存的是一個指向堆區的指標,通過這個指標,就可以找到這個例項在堆區對應的物件。因此,普通型別變數只在棧區佔用一塊記憶體,而引用型別變數要在棧區和堆區各佔一塊記憶體。

執行

===========================================================================================================================================

  1. JVM自動尋找main方法,執行第一句程式碼,建立一個Test類的例項,在棧中分配一塊記憶體,存放一個指向堆區物件的指標110925。

  2. 建立一個int型的變數date,由於是基本型別,直接在棧中存放date對應的值9。

  3. 建立兩個BirthDate類的例項d1、d2,在棧中分別存放了對應的指標指向各自的物件(物件在堆中)。他們在例項化時呼叫了有引數的構造方法,因此物件中有自定義初始值。

  4. 但是注意有一個change1()方法。 呼叫test物件的change1方法,並且以date為引數。JVM讀到這段程式碼時,檢測到i是區域性變數,因此會把i放在棧中,並且把date的值賦給i。

  5. change1方法執行完畢,立即釋放區域性變數i所佔用的棧空間。

  6. 但是如果一個方法必須change2(Person p)需要的是一個物件,而且這個物件是在方法區域內產生的,這時記憶體情況又該如何分配? change2方法中又例項化了一個Person物件,並且賦給p。在內部執行過程是:在堆區new了一個物件,並且把該物件的指標儲存在棧中的p對應空間

  7. change2方法執行完畢,立即釋放區域性引用變數b所佔的棧空間,注意只是釋放了棧空間,堆空間要等待自動回收。

  8. 如果Person p1 =new Person() p1.name =“張三”; test.changName(p1);傳入的是一個物件 但是: public void changeName (Person bb){ bb.setName =“李四” } 並沒有像6中那樣建立一個物件,這時記憶體該如何分配呢?

  9. 呼叫test例項的change3方法,以例項d2為引數。同理,JVM會在棧中為區域性引用變數b分配空間,並且把d2中的指標存放在b中,此時d2和b指向同一個物件。再呼叫例項b的setDay方法,其實就是呼叫d2指向的物件的setDay方法。 呼叫例項b的setDay方法會影響d2,因為二者指向的是同一個物件。 change3方法執行完畢,立即釋放區域性引用變數b。

以上就是Java程式執行時記憶體分配的大致情況。其實也沒什麼,掌握了思想就很簡單了。無非就是兩種型別的變數:基本型別和引用型別。二者作為區域性變數,都放在棧中,基本型別直接在棧中儲存值,引用型別只儲存一個指向堆區的指標,真正的物件在堆裡。作為引數時基本型別就直接傳值,引用型別傳指標。

小結:

1.分清什麼是例項什麼是物件。Class a= new Class();此時a叫例項,而不能說a是物件(new Class()才是物件)。例項在棧中,物件在堆中,操作例項實際上是通過例項的指標間接操作物件。多個例項可以指向同一個物件。

2.棧中的資料和堆中的資料銷燬並不是同步的。方法一旦結束,棧中的區域性變數立即銷燬,但是堆中物件不一定銷燬。因為可能有其他變數也指向了這個物件,直到棧中沒有變數指向堆中的物件時,它才銷燬,而且還不是馬上銷燬,要等垃圾回收掃描時才可以被銷燬。

  3.以上的棧、堆、程式碼段、資料段等等都是相對於應用程式而言的。每一個應用程式都對應唯一的一個JVM例項,每一個JVM例項都有自己的記憶體區域,互不影響。並且這些記憶體區域是所有執行緒共享的。這裡提到的棧和堆都是整體上的概念,這些堆疊還可以細分。

   4.類的成員變數在不同物件中各不相同,都有自己的儲存空間(成員變數在堆中的物件中)。而類的方法卻是該類的所有物件共享的,只有一套,物件使用方法的時候方法才被壓入棧,方法不使用則不佔用記憶體。

以上分析只涉及了棧和堆,還有一個非常重要的記憶體區域:常量池,這個地方往往出現一些莫名其妙的問題。常量池是幹嘛的上邊已經說明了,也沒必要理解多麼深刻,只要記住它維護了一個已載入類的常量就可以了。接下來結合一些例子說明常量池的特性。

預備知識:

基本型別和基本型別的包裝類。

基本型別有:byte、short、char、int、long、boolean。

基本型別的包裝類分別是:Byte、Short、Character、Integer、Long、Boolean。

注意區分大小寫。

二者的區別是:基本型別體現在程式中是普通變數,基本型別的包裝類是類,體現在程式中是引用變數。

因此二者在記憶體中的儲存位置不同:基本型別儲存在棧中,而基本型別包裝類儲存在堆中。上邊提到的這些包裝類都實現了常量池技術,另外兩種浮點數型別的包裝類則沒有實現。另外,String型別也實現了常量池技術。 什麼是成員變數?成員方法?