天天看點

JVM虛拟機結構

JVM的主要結構如下圖所示,圖檔引用自舒の随想日記。

方法區和堆由所有線程共享,其他區域都是線程私有的

程式計數器(Program Counter Register)

類似于PC寄存器,是一塊較小的記憶體區域,通過程式計數器中的值尋找要執行的指令的位元組碼,由于多線程間切換時要恢複每一個線程的目前執行位置,是以每個線程都有自己的程式電腦。這一個區域不會有OutOfMemeryError。當執行Java方法時,這裡存儲的執行的指令的位址,如果執行的是本地方法,這裡的值是Undefined。

虛拟機棧(Java Stack)

虛拟機棧也是線程私有的,每建立一個線程,虛拟機就會為這個線程建立一個虛拟機棧,虛拟機棧表示Java方法執行的記憶體模型,每調用一個方法,就會生成一個棧幀(Stack Frame)用于存儲方法的本地變量表、操作棧、方法出口等資訊,當這個方法執行完後,就會彈出相應的棧幀。

如果請求的棧的深度過大,虛拟機可能會抛出StackOverflowError異常,如果虛拟機的實作中允許虛拟機棧動态擴充,當記憶體不足以擴充棧的時候,會抛出OutOfMemoryError異常。

棧幀(Stack Frame)

棧幀分為三部分:局部變量區(Local Variables)、操作數棧(Operand Stack)和幀資料區(Frame Data)。

局部變量區(Loca Variables)

局部變量區被組織一個一個從0開始的字數組,byte、short、char在存儲前被轉換為int,boolean也被轉換為int,0表示false,非0表示true,long和double占據兩個字長。

操作數棧(Operand Stack)

操作數棧也被組織為一個字數組,但不同于局部變量區,它不是通過數組下标通路的,而是能過棧的Push和Pop操作,前一個操作Push進的資料可以被下一個操作Pop出來使用。

幀資料區(Frame Data)

這部分的作用主要有三部分:

  • 常量池中資料的解析
  • 方法執行完後處理方法傳回,恢複調用方現場
  • 方法執行過程中抛出異常時的異常處理,存儲有一個異常表,當出現異常時虛拟機查找相應的異常表看是否有對應的Catch語句,如果沒有就抛出異常終止這個方法調用

本地方法棧(Native Method Stack)

與虛拟機棧類似,隻是是執行本地方法時使用的。

方法區(Method Area)

用于存儲已被虛拟機加載的類型資訊、常量、靜态變量、即時編譯後的代碼等資訊。

方法區是線程間共享的,當兩個線程同時需要加載一個類型時,隻有一個類會請求ClassLoader加載,另一個線程會等待。

對于每一個加載的類型,會在方法區中儲存以下資訊:

  • 類及其父類的全限定名(java.lang.Object沒有父類)
  • 類的類型(Class or Interface)
  • 通路修飾符(public, abstract, final)
  • 實作的接口的全限定名的清單
  • 常量池
  • 字段資訊
  • 方法資訊
  • 除常量外的靜态變量
  • ClassLoader引用
  • Class引用

對于每一個字段,會在方法區中儲存以下資訊(字段聲明順序也會儲存):

  • 字段名
  • 字段的類型
  • 字段的修飾符(public, private , protected, static, final, volatile, transient)

對于每一個方法,會在方法區中儲存以下資訊(方法聲明順序也會儲存):

  • 方法名
  • 方法傳回類型(或void)
  • 參數資訊
  • 方法修飾符(public, private, protected , static, final, synchronized, native, abstract)

如果方法不是抽象方法并不是本地方法(Native Method),還會儲存以下資訊:

  • 方法的位元組碼
  • 本地變量表及操作數棧的大小
  • 異常表

虛拟機需要存儲一些資料,用來快速地通路一個類對象中的方法,一般實作為一個方法表。

方法區中還有一部分是運作時常量池,主要用來存儲編譯時生成的字面量和符号引用,常量也可以在運作時産生,如String的intern方法。

方法區中也可能存在GC,但虛拟機規範對此不做要求,主要是回收一些常量和解除安裝一些不用的類型資訊,不過要解除安裝一個類的條件很難達到,而且些處GC其實也回收不了多少記憶體。

堆(Heap)

虛拟機中用于存放對象與數組執行個體的地方,垃圾回收的主要區域就是這裡(還可能有方法區)。

如果垃圾收集算法采用按代收集(目前大都是這樣),這部分還可以細分為新生代和老年代。

新生代又可能分為Eden區,From Survivor區和To Survivor區,主要是為了垃圾回收。所有的線程共享Java堆,在這裡還可以劃分線程私有的緩沖區(Thread Local Allocation Buffer,TLAB)。

Java堆隻要求邏輯上是連續的,在實體空間上可以不連續。

直接記憶體

JDK1.4中引用了NIO,并引用了Channel與Buffer,可以使用Native函數庫直接配置設定堆外記憶體,并通過一個存儲在Java堆裡面的DirectByteBuffer對象作為這塊記憶體的引用進行操作。

對象通路

當建立一個對象時,會在堆中為這個對象配置設定記憶體,并在棧中有一個對這個對象引用,除此之外,在Java堆中還要能通過這個對象找到它的類型資訊(對象類型,父類,實作的接口,包含的字段與方法等)。

Reference在Java虛拟機中定義為指向對象的引用,但沒有定義這個Reference應該有怎麼實作。

一種實作是Reference直接存儲對象在堆内的位址,對象的類型資訊可以在對象在堆中的記憶體布局中存儲,如存儲在對象記憶體的開頭等。

另一種實作是Reference指向一個句柄表中的一個位置,句柄中儲存了對象的實際位置及它對應的類型資訊。

使用句柄的好處是當在記憶體中移動對象的位置時,隻需要更新句柄表中的内容,不需要改變引用值,但會多一次記憶體通路開銷,直接引用的優缺點與此相反。

參考資料:

  1. 深入Java虛拟機
  2. 深入了解Java虛拟機-JVM進階特性與最佳實踐
  3. 淺析Java虛拟機結構與機制

作者:AngelDevil

出處:www.cnblogs.com/angeldevil

轉載請注明出處!