JVM虛擬機結構

JVM的主要結構以下圖所示,圖片引用自舒の隨想日記html

JVM結構

方法區和堆由全部線程共享,其餘區域都是線程私有的 java

程序計數器(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出來使用。 spa

幀數據區(Frame Data)

這部分的做用主要有三部分: .net

  • 常量池中數據的解析
  • 方法執行完後處理方法返回,恢復調用方現場
  • 方法執行過程當中拋出異常時的異常處理,存儲有一個異常表,當出現異常時虛擬機查找相應的異常表看是否有對應的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虛擬機結構與機制
相關文章
相關標籤/搜索