1. 程式人生 > >JVM(一)JVM記憶體結構

JVM(一)JVM記憶體結構

Java程式碼需要執行在虛擬機器(JVM)上,而JVM為了方便管理記憶體,會把自己所管理的記憶體劃分為若干個不同的資料區域,用作不同的用途,先看一下大致劃分

存放內容:

    ·大多數建立的物件

    ·陣列值

GC情況:

    GC工作的主要區域,回收不再被使用的物件

記憶體溢位:

    當需要為物件分配記憶體時,堆的記憶體佔用已達到了設定的最大值,則丟擲OutOfMenoryError

引數設定:

    -Xms最小堆記憶體,預設為實體記憶體的1/64,但小於1G

    -Xmx最大堆記憶體,預設為實體記憶體的1/4,但小於1G

注意:

    實際使用時,設定最大堆記憶體=最小堆記憶體,這樣堆記憶體就不會頻繁調整了

方法區

存放內容:

    ·已載入的類的資訊(名稱、修飾符等)

    ·靜態變數(static)、常量(final)

    ·類中的方法資訊

    ·執行時常量池(編譯器生成的各自字面量和符號引用儲存在class檔案的常量池中,即所說的靜態常量池,這些內容會在類載入之後進入執行時常量池)JDK1.6常量池在方法區,1.7在堆

GC情況:

    ·回收未被引用的類

    ·堆常量池的回收

記憶體溢位:

    當類載入器載入class檔案到記憶體中時,提取的類資訊要存放在方法區,而方法區的記憶體佔用已達到了設定的最大值,則丟擲OutOfMenoryError

引數設定:

    -XX:permSize方法區最小值,預設為16M  

    -XX:MaxPermSize方法區最大值,預設為64M

虛擬機器棧

棧中的

存放內容: 

    ·區域性變量表:八大基本資料型別資料、物件引用(該空間在編譯期已經分配好,執行期不變

)

    ·運算元棧

執行狀況:    

    每建立一個執行緒就會對應建立一個虛擬機器棧,棧中有多個棧幀,每呼叫一個方法就往棧中建立一個棧幀,棧幀是用來存放方法資料和部分過程結果的資料結構,每一個方法從呼叫到最終返回結果的過程,就對應一個棧幀從入棧到出棧的過程

    執行緒執行過程中,只有一個棧幀處於活躍狀態,稱為“當前活動棧幀”(對應方法為當前方法,類為當前類),當前活動棧幀始終是虛擬機器棧的棧頂元素

GC情況:

    棧的生命週期和執行緒同步,隨著執行緒銷燬或結束,它們佔用的記憶體會自動釋放,不需要GC

記憶體溢位/洩露:

    1、Java程式啟動一個新的執行緒,而沒有足夠的空間為該執行緒分配一個棧時,會丟擲OutOfMenoryError

    2、當執行緒呼叫一個方法時,JVM壓入一個新的棧幀到這個執行緒對應的棧中,只要這個方法還沒返回,這個棧幀就存在,如果方法巢狀的呼叫層數太多,如遞迴,隨著棧幀的增多,最終導致這個執行緒棧中的棧幀總大小超出-Xss所設定大小,丟擲StackOverFlowError

大小設定:

    -Xss設定每個執行緒的堆疊大小,通常1M即可

本地方法棧

    和虛擬機器棧作用相似,不過虛擬機器棧為Java方法提供服務,本地方法棧為Native方法提供服務

程式計數器(PC暫存器)

存放內容:

    儲存當前執行緒執行的虛擬機器位元組碼指令的記憶體地址

GC情況:

    唯一一個沒有規定任何OutOfMenory情況的區域

    

記憶體結構的定義是由JVM所決定的,所以JVM產商不同,記憶體結構會稍有不同,不過大體上都受《Java虛擬機器規範》的約束。此處我們提幾個比較重要的點:

  1. 規範中定義的方法區,只是一種概念上的區域,只說明瞭它應該具有什麼樣的功能,但並沒有規定這個區域應該處於何處,所以不同的虛擬機器可能實現不同;
  2. 不同版本JDK的方法區所處位置不同,上面的圖劃分了邏輯區域,並不是物理區域,比如某些版本的JDK中,方法區是在堆中實現的;
  3. 執行時常量池用於存放編譯期生成的各種字面量(數字)和符號應用,但Java並不要求常量只有在編譯期才能產生,比如在執行期,String.intern也會把新的常量放入池中;
  4. Java程式執行期間除了以上描述的記憶體外,還有一塊記憶體區域可供使用,即直接記憶體。《Java虛擬機器規範》並沒有定義它,因為它並不由JVM進行管理,它是由本地方法庫直接在堆外申請的記憶體區域;
  5. 堆和棧的資料劃分也不是絕對的,比如HotSpot的JIT會針對物件分配做相應的優化。

 

例項解析:

//例1
Integer i1 = 40;
Integer i2 = 40;
Integer i3 = new Integer(40);
System.out.println("i1=i2 ?" + (i1==i2));//true  因為Java建立了[-128,127]的快取資料,會直接引用該值在常量池的地址
System.out.println("i1=i3 ?" + (i1==i3));//false new出來的物件在堆中,而i1在常量池

Integer i4 = i2 + 0;
Integer i5 = new Integer(0);
Integer i6 = i3 + i5;
Integer i7 = i2 + i5;
System.out.println("i1=i4 ?" + (i1==i4));//true  
System.out.println("i1=i6 ?" + (i1==i6));//true  因為Java數學計算都先進行拆箱,這裡的i6就相當於Integer i6 = 40;
System.out.println("i1=i7 ?" + (i1==i7));//true  同上
//例2
int j0 = 400;
Integer j1 = 400;
Integer j2 = 400;
Integer j3 = new Integer(400);
System.out.println("j0=j1 ?" + (j0==j1));//true  因為Integer會自動拆箱成int
System.out.println("j1=j2 ?" + (j1==j2));//false 因為超過了127,所以會自動裝箱成new Integer
System.out.println("j1=j3 ?" + (j1==j3));//false j1和j3都是new Integer

Integer j4 = 0;
Integer j5 = j2 + j4;
Integer j6 = new Integer(0);
Integer j7 = j3 + j6;
Integer j8 = j2 + j6;
System.out.println("j1=j5 ?" + (j1==j5));//false  
System.out.println("j1=j7 ?" + (j1==j7));//false
System.out.println("j1=j8 ?" + (j1==j8));//false
//例3
String a = "我只是一個字串比較長的字串";
String b = "我只是一個字串比較長的字串";
String c = new String("我只是一個字串,比較長的字串");
System.out.println("a=b ?" + (a==b));  //true  直接存入常量池
System.out.println("a=c ?" + (a==c));  //false 堆與常量池的比較
    	
String d = "我只是一個字串";
String e = "比較長的字串";
String f = d + e;
String g = "我只是一個字串" + "比較長的字串";
System.out.println("a=f ?" + (a==f));  //false
System.out.println("a=g ?" + (a==g));  //true
//關於最後兩條的解釋,可以看下底部參考文章中的《觸控java常量池》中的解釋

 

參考文章

JVM記憶體結構 VS Java記憶體模型 VS Java物件模型

JVM記憶體結構

觸控java常量池