JVM的基本結構及其各部分詳解2

3.2 棧幀組成之操做數棧java

操做數棧是棧幀的主要內容之一,它主要用於保存計算過程當中的中間結果,同時做爲計算過程當中變量臨時的存儲空間。數據結構

操做數棧也是一個先進後出的數據結構,只支持入棧和出棧兩種操做,許多java字節碼指令都須要經過操做數棧進行參數傳遞。好比add指令,它就會在操做數棧中彈出兩個整數並進行加法計算,計算結果會被入棧,如圖:顯示了iadd先後操做數棧的變化。jvm

3.3 幀數據區函數

除了局部變量表和操做數棧,java棧幀還須要一些數據來支持常量池的解析、正常方法返回和異常處理等。大部分java字節碼指令須要進行常量池訪問,在幀數據區中保留着訪問常量池的指針,方便程序訪問常量池。性能

此外,當函數返回或者出現異常時,虛擬機必須恢復調用者函數的棧幀,並讓調用者函數繼續執行下去。對於異常處理,虛擬機必須有一個異常處理表,方便在發生異常時找處處理異常的代碼,所以異常處理表也是幀數據區中重要的一部分,一個典型的異常處理表以下所示:測試

Exception table:優化

from   to    target   typespa

4        16      19      any線程

19       21      19      any代理

它表示在字節碼偏移量4--16字節可能拋出任意異常,若是拋出異常,則跳轉到字節碼偏移量19處執行。當方法拋出異常時,虛擬機就會查找相似的異常表來處理,若是沒法在異常表中找到合適的處理方法,則會結束當前函數調用,返回調用函數,並在調用函數中拋出相同的異常,並查找調用函數的異常表來進行處理。

3.4 棧上分配

棧上分配是java虛擬機提供的一項優化技術,它的基本思想是,對於那些線程私有的對象(這裏指不可能被其餘線程訪問的對象),能夠將他們打散分配到棧上,而不是分配到堆上。分配到棧上的好處是能夠在函數調用結束後自行銷燬,而不須要垃圾回收器的介入,從而提升系統的性能。

棧上分配的一個技術基礎是進行逃逸分析,逃逸分析的目的是判斷對象的做用域是否有可能逃逸出函數體。以下代碼所示顯示了一個逃逸對象:

private static User u;

public static void alloc(){

  u = new User();

  u.id = 5;

  u.name = "jim";

}

對象u是類的成員變量,該字段有可能被任何線程訪問,所以屬於逃逸對象,而如下對象顯示了一個非逃逸對象:

public static void alloc(){

  User u = new User();

  u.id =  5;

  u.name = "jim";

}

在上述代碼中,對象User u 以局部變量的形式存在,而且該對象並無被alloc()函數返回或者出現任何形式的公開,所以它未發生逃逸,因此對於這種狀況,虛擬機就有可能將User u 分配在棧上,而不是在堆上。

對於大量的零散小對象,棧上分配提供了一種良好的對象分配優化策略,棧上分配速度快,而且能夠有效避免垃圾回收帶來的負面影響。但因爲棧和堆空間相比,棧空間較小,所以對於大對象沒法也不適合在棧上分配。

實例1:測試非逃逸對象的分配空間位置

package com.jvm;

public class OnStackTest {
  public static class User{
    public int id = 0;
    public String name = "";
  }

  public static void alloc(){
    User u = new User();
    u.id = 5;
    u.name = "jim";
  }

  public static void main(String[] args) {
    long b = System.currentTimeMillis();
    for(int i=0;i<100000000;i++){
      alloc();
    }
    long e = System.currentTimeMillis();
    System.out.println(e-b);
  }
}

使用-Xmx10M -XX:+PrintGC 虛擬機參數運行代碼:

[GC (Allocation Failure) 2048K->544K(9728K), 0.0015011 secs]
10

上述代碼在主函數中進行了1億次alloc()調用進行對象的建立,因爲User對象實例須要佔用約16byte的空間,所以累計分配空間將達到1.5G,若是堆空間小於這個值,就必然發生GC。而此時咱們只分配了最大的堆內存爲10M,若是這些對象在堆上建立,必然會引發大量的垃圾回收現象,查看垃圾回收日誌,並無。因此,說明其對象分配在棧上。

實例2:對比測試逃逸對象的分配空間位置:

package com.jvm;

public class OnStackTest {
  public static class User{
    public int id = 0;
    public String name = "";
  }
  public static User u;
  public static void alloc(){
    u = new User();
    u.id = 5;
    u.name = "jim";
  }

  public static void main(String[] args) {
    long b = System.currentTimeMillis();
    for(int i=0;i<100000000;i++){
      alloc();
    }
    long e = System.currentTimeMillis();
    System.out.println(e-b);
  }
}

一樣使用虛擬機參數-Xmx10M -XX:+PrintGC設置最大堆空間和打印垃圾回收日誌,運行此代碼:

可見,發生大量的垃圾回收現象,說明此時堆內存遠遠不夠,須要不斷的進行垃圾回收。

 

4 方法區

和堆同樣,方法區是一塊全部線程共享的內存區域,它用於保存系統的類信息,好比類的字段、方法、常量池等。方法區的大小決定了系統能夠保存多少個類,若是系統定義了太多的類,致使方法區的溢出,虛擬機一樣會拋出內存溢出錯誤。

在JDK1.六、JDK1.7中,方法區能夠理解爲永久區(Perm)。永久區可使用參數-XX:PermSize和-XX:MaxPermSize指定,默認狀況下,-XX:MaxPermSize爲64M。一個大的永久區能夠保存更多的類信息。若是系統使用了一些動態代理,那麼有可能會在運行時生成大量的類,若是這樣,就須要設置一個合理的永久區大小,確保不發生永久區內存溢出。

 

在JDK1.8中,永久區已經被完全移除,取而代之的是元數據區,元數據區大小可使用參數-XX:MaxMetaspaceSize指定(一個大的元數據區可使系統支持更多的類),這是一塊堆外的直接內存。與永久區不一樣,若是不指定大小,默認狀況下,虛擬機會耗盡全部的可用系統內存。

若是元數據區發生異常,虛擬機同樣會拋出異常。

相關文章
相關標籤/搜索