1. 程式人生 > >java記憶體分配之堆,棧,常量池,方法區

java記憶體分配之堆,棧,常量池,方法區

java棧

java棧,在函式的定義中定義的基本型別(int,long,short,byte,float,double,boolean,char)的變數資料和物件的引用變數分配的儲存空間的地方。當在程式碼塊中定義一個變數時,java棧就為這個變數分配適當的記憶體空間,當該變數退出作用域時,java棧會釋放該變數的記憶體空間。

java堆

Java堆,用來存放new建立的物件(例項)和陣列。該記憶體的回收由java虛擬機器的自動垃圾回收器管理。

在java堆中新建立了一個物件或者陣列後,可以在java棧中定義一個特殊的變數,可以讓該變數指向java堆中得陣列或者物件的首地址,Java棧中的該變數就變成了Java堆陣列或者物件的引用,該引用就是為java堆中的物件和陣列起了一個名字,以後在程式的使用中可以使用該引用來呼叫物件例項和陣列。

引用變數java棧中分配記憶體,當程式執行到其作用於之外的區域後,該引用變數被釋放。而陣列和物件本身在堆中分配,即使程式執行到使用 new 產生陣列或者物件的語句所在的程式碼塊之外,陣列和物件本身佔據的記憶體不會被釋放,陣列和物件在沒有引用變數指向它的時候,才變為垃圾,不能在被使用,但仍 然佔據記憶體空間不放,在隨後的一個不確定的時間被垃圾回收器收走(釋放掉)。這也是 Java 比較佔記憶體的原因。 

堆疊可以理解為指標,Java棧時地址,Java對時地址指向的變數單元。

常量池 

  常量池指的是在編譯期被確定,並被儲存在已編譯的.class檔案中的一些資料。除了包含程式碼中所定義的各種基本型別(如int、long等等)和物件型(如String及陣列)的常量值(final)還包含一些以文字形式出現的符號引用,比如: 

  1. 類和介面的全限定名;
  2. 欄位的名稱和描述符; 
  3. 方法和名稱和描述符。

  虛擬機器必須為每個被裝載的型別維護一個常量池。常量池就是該型別所用到常量的一個有序集和,包括直接常量(string,integer和 floating point常量)和對其他型別,欄位和方法的符號引用。

  對於String常量,它的值是在常量池中的。而JVM中的常量池在記憶體當中是以表的形式存在的, 對於String型別,有一張固定長度的CONSTANT_String_info表用來儲存文字字串值,注意:該表只儲存文字字串值,不儲存符號引 用。說到這裡,對常量池中的字串值的儲存位置應該有一個比較明瞭的理解了。

  在程式執行的時候,常量池儲存在Method Area,而不是堆中(永遠不在堆中)。

下面對字串String的記憶體分配和基本資料型別的分配舉例說名。

方法區

方法區也是各個執行緒共享的記憶體區域,它用於儲存已經被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。方法區域又被稱為“永久代”,但這僅僅對於 Sun HotSpot 來講,JRockit 和 IBM J9 虛擬機器中並不存在永久代的概念。Java 虛擬機器規範把方法區描述為 Java 堆的一個邏輯部分,而且它和 Java Heap 一樣不需要連續的記憶體,可以選擇固定大小或可擴充套件,另外,虛擬機器規範允許該區域可以選擇不實現垃圾回收。相對而言,垃圾收集行為在這個區域比較少出現。該區域的記憶體回收目標主要針是對廢棄常量的和無用類的回收。執行時常量池是方法區的一部分,Class 檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池(Class檔案常量池),用於存放編譯器生成的各種字面量和符號引用,這部分內容將在類載入後存放到方法區的執行時常量池中。執行時常量池相對於 Class 檔案常量池的另一個重要特徵是具備動態性,Java 語言並不要求常量一定只能在編譯期產生,也就是並非預置入 Class 檔案中的常量池的內容才能進入方法區的執行時常量池,執行期間也可能將新的常量放入池中,這種特性被開發人員利用比較多的是 String 類的 intern()方法。

物件例項化分析

對記憶體分配情況分析最常見的示例便是物件例項化:

Object obj = new Object()

這段程式碼的執行會涉及 Java 棧、Java 堆、方法區三個最重要的記憶體區域。假設該語句出現在方法體中,及時對 JVM 虛擬機器不瞭解的 Java 使用這,應該也知道 obj 會作為引用型別(reference)的資料儲存在 Java 棧的本地變量表中,而會在 Java 堆中儲存該引用的例項化物件,但可能並不知道,Java 堆中還必須包含能查詢到此物件型別資料的地址資訊(如物件型別、父類、實現的介面、方法等),這些型別資料則儲存在方法區中。

String字串的記憶體分配

    這裡需要有個說明:new 是在執行時才分配記憶體空間。編譯只是檢查語法。

 對於字串,其物件的引用都是儲存在棧中的,如果是編譯期已經建立好(直接用雙引號定義的)的就儲存在常量池中,如果是執行期(new出來的)才能確定的就儲存在堆中。對於equals相等的字串,在常量池中永遠只有一份,在堆中有多份。


        String s1 = "china";
        String s2 = "china";
        String s3 = "china";

        String ss1 = new String("china");
        String ss2 = new String("china");
        String ss3 = new String("china");

 

 

可以看出:

      String X= " ",s1,s2,s3是在編譯時就被建立,並且放入常量池中 ,以String X="china"為例在編譯期間,編譯器會檢視常量池中是否存在china,如果不存在會new出一個china字串,存在不會new,會讓棧中的引用變數指向已經存在的china字串,因此常量池只有一個常量被建立。

        對於 String X = new String("china"),在編譯時不會被建立,在載入和執行過程中,通過new產生一個字串(假設為“china”)時,會先去常量池中查詢是否已經有了“china”物件,如果沒有則在常量池中建立一個此字串物件,然後堆中再建立一個常量池中此”china”物件的拷貝物件。也就是說new的時候,實際是再記憶體中開闢兩個地方存放“abc”,一個在堆記憶體上,一個在常量池。

這也就是有道面試題:Strings=newString(“xyz”);產生幾個物件?一個或兩個,如果常量池中原來沒有”xyz”,就是兩個。

基礎型別的變數和常量


        int i1 = 9;
        int i2 = 9;
        int i3 = 9;

        final int INT1 = 9;
        final int INT2 = 9;
        final int INT3 = 9;

 

 編譯器先處理int i1 = 9;首先它會在棧中建立一個變數為i1的引用,然後查詢棧中是否有9這個值,如果沒找到,就將9存放進來,然後將i1指向9。接著處理int i2 = 9;在建立完i2的引用變數後,因為在棧中已經有9這個值,便將i2直接指向9。這樣,就出現了i1與i2同時均指向9的情況。最後i3也指向這個9。重複一句棧是用來存放基礎型別資料和 物件的引用。

成員變數和區域性變數在記憶體中的分配

 對於成員變數和區域性變數:成員變數就是方法外部,類的內部定義的變數;區域性變數就是方法或語句塊內部定義的變數。區域性變數必須初始化。 形式引數是區域性變數,區域性變數的資料存在於棧記憶體中。棧記憶體中的區域性變數隨著方法的消失而消失。 成員變數儲存在堆中的物件裡面,由垃圾回收器負責回收。

class BirthDate {
    private int day;
    private int month;
    private int year;

    public BirthDate(int d, int m, int y) {
        day = d;
        month = m;
        year = y;
    }
    // 省略get,set方法………
}

public class Test {
    public static void main(String args[]) {
        int date = 9;
        Test test = new Test();
        test.change(date);
        BirthDate d1 = new BirthDate(7, 7, 1970);
    }

    public void change(int i) {
        i = 1234;
    }
}

 

 

對於以上這段程式碼,date為區域性變數,i,d,m,y都是形參為區域性變數,day,month,year為成員變數。下面分析一下程式碼執行時候的變化:    

  1. main方法開始執行:int date = 9; date區域性變數,基礎型別,引用和值都存在棧中。
  2. Test test = new Test();test為物件引用,存在棧中,物件(new Test())存在堆中。 
  3. test.change(date);  i為區域性變數,引用和值存在棧中。當方法change執行完成後,i就會從棧中消失。
  4. BirthDate d1= new BirthDate(7,7,1970); d1為物件引用,存在棧中,物件(new BirthDate())存在堆中,其中d,m,y為區域性變數儲存在棧中,且它們的型別為基礎型別,因此它們的資料也儲存在棧中。day,month,year為成員變數,它們儲存在堆中(new BirthDate()裡面)。當BirthDate構造方法執行完之後,d,m,y將從棧中消失。 
  5. main方法執行完之後,date變數,test,d1引用將從棧中消失,new Test(), new BirthDate()將等待垃圾回收.

 

參考:https://www.cnblogs.com/SaraMoring/p/5687466.html