Java虛擬機(一)結構原理與運行時數據區域

前言

原本計劃要寫Android內存優化的,以爲有必要在此以前介紹一下Java虛擬機的相關知識,Java虛擬機也並非三言兩語可以介紹完的,所以開了Java虛擬機系列,這一篇文章咱們來學習Java虛擬機的結構原理與運行時數據區域。html

1.Java虛擬機概述

Oracle官方定義的Java技術體系主要包括如下幾個部分:java

  • Java程序設計語言
  • 各類平臺的Java虛擬機
  • Class文件格式
  • Java API類庫
  • 第三方Java類庫

能夠把Java程序設計語言、Java虛擬機和Java API類庫這三部分統稱爲JDK(Java Development Kit),它是Java程序開發的最小環境。另外,Java API中的Java SE API子集和Java虛擬機這兩部分統稱爲JRE(Java Runtime Environment),它是Java程序運行的標準環境。
從上面能夠看出Java虛擬機及其重要,它是整個Java平臺的基石,是Java語言編譯代碼的運行平臺。你能夠把Java虛擬機看作一個抽象的計算機,它有各類指令集和各類運行時數據區域。數據結構

1.1 Java虛擬機家族

不少同窗可能認爲Java虛擬機,就是一個虛擬機而已,它還有家族?或者認爲Java虛擬機指的就是Oracle的HotSpot虛擬機。這裏來簡單介紹Java虛擬機家族,自從1996年Sun公司發佈的JDK1.0中包含的Sun Classic VM到今天,出現和消亡了不少種虛擬機,咱們這裏只簡單介紹目前存活的相對主流Java虛擬機。多線程

HotSpot VM
Oracle JDK和OpenJDK中自帶的虛擬機,是最主流的和使用範圍最廣的Java虛擬機。介紹Java虛擬機的技術文章,若是不作特殊說明,大部分都是介紹HotSpot VM的。HotSpot VM並不是是Sun公司開發的,而是由Longview Technologies這家小公司設計的,它在1997年被Sun公司收購,Sun公司又在2009年被Oracle收購。
J9 VM
J9 VM 是IBM開發的VM,目前是其主力發展的Java虛擬機。J9 VM的市場定位和HotSpot VM接近,它是一款設計上從服務端到桌面應用再到嵌入式都考慮到的多用途虛擬機,目前J9 VM的性能水平大體跟HotSpot VM是一個檔次的。
Zing VM
以Oracle的HotSpot VM爲基礎,改進了許多會影響延遲的細節。最大的三個賣點是:jvm

  • 1.低延遲,「無暫停」的C4 GC,GC帶來的暫停能夠控制在10ms如下的級別,支持的Java堆大小能夠到1TB;
  • 2.啓動後快速預熱功能。
  • 3.可管理性:零開銷、可在生產環境全時開啓的、整合在JVM內的監控工具Zing Vision。

1.2 Java虛擬機執行流程

當咱們執行一個Java程序時,它的執行流程是怎樣的呢?以下圖所示。工具

Java程序執行流程(2).png

從上圖能夠看到Java虛擬機與java語言沒有什麼必然聯繫,它只與特定的二進制文件:Class文件有關。性能

2.Java虛擬機結構

這裏所講的體系結構,是指的Java虛擬機的抽象行爲,而不是具體的好比HotSpot VM的實現。按照Java虛擬機規範,抽象的Java虛擬機以下圖所示。學習

Java虛擬機體系結構(3).png

2.1 Class文件格式

Java文件被編譯後生成了Class文件,這種二進制格式文件不依賴於特定的硬件和操做系統。每個Class文件中都對應着惟一一個類或者接口的的定義信息,可是類或者接口並不必定定義在文件中,好比類和接口能夠經過類加載器來直接生成。優化

ClassFile的文件結構以下所示。this

ClassFile {
    u4 magic;                                     //魔數,固定值爲0xCAFEBABE,用來判斷當前文件是能被Java虛擬機處理的Class文件
    u2 minor_version;                             //副版本號
    u2 major_version;                             //主版本號
    u2 constant_pool_count;                       //常量池計數器
    cp_info constant_pool[constant_pool_count-1]; //常量池
    u2 access_flags;                              //類和接口層次的訪問標誌
    u2 this_class;                                //類索引
    u2 super_class;                               //父類索引
    u2 interfaces_count;                          //接口計數器
    u2 interfaces[interfaces_count];              //接口表
    u2 fields_count;                              //字段計數器
    field_info fields[fields_count];              //字段表
    u2 methods_count;                             //方法計數器
    method_info methods[methods_count];           //方法表
    u2 attributes_count;                          //屬性計數器
    attribute_info attributes[attributes_count];  //屬性表
}複製代碼

2.2 類加載器子系統

類加載器子系統經過多種類加載器來查找和加載Class文件到 Java 虛擬機中。Java虛擬機有兩種類加載器:系統加載器和用戶自定義加載器。其中系統加載器包括如下三種:

  • 引導類加載器(Bootstrap Class Loader):用C/C++代碼實現的加載器,用以加載Java虛擬機運行時所須要的系統類,這些系統類在{JRE_HOME}/lib目錄下。Java虛擬機的啓動就是經過引導類加載器建立一個初始類來完成的。因爲類加載器是使用平臺相關的底層C/C++語言實現的, 因此該加載器不能被Java代碼訪問到。可是,咱們能夠查詢某個類是否被引導類加載器加載過。引導類裝載器並不繼承java.lang.ClassLoader。
  • 擴展類加載器(Extensions Class Loader):用於加載 Java 的拓展類 ,拓展類通常會放在 {JRE_HOME}/lib/ext/ 目錄下,用來提供除了系統類以外的額外功能。
  • 應用程序類加載器(Application Class Loader):該類加載器是用於加載用戶代碼,是用戶代碼的入口。應用類加載器將拓展類加載器當成本身的父類加載器,當嘗試加載類的時候,首先嚐試讓拓展類加載器加載,若是拓展類加載器加載成功,則直接返回加載結果Class instance,若是加載失敗,則會詢問引導類加載器是否已經加載了該類,若是沒有,應用類加載器纔會嘗試本身加載。

用戶自定義加載器,則是經過繼承 java.lang.ClassLoader類的方式來實現本身的類加載器。
類加載器子系統除了要加載Class文件類到 Java 虛擬機中,還必須負責驗證被導入的Class類的正確性,爲類變量分配並初始化內存,以及幫助解析符號引用。這些動做必須嚴格按如下順序進行:

1.裝載:查找並加載Class文件。
2.連接:驗證、準備、以及解析。

  • 驗證:確保被導入類型的正確性。
  • 準備:爲類的靜態字段分配字段,並用默認值初始化這些字段。
  • 解析:根據運行時常量池的符號引用來動態決定具體值得過程。

3.初始化:將類變量初始化爲正確初始值。

2.3 數據類型

Java虛擬機與Java語言的數據類型類似,能夠分爲兩類:基本類型和引用類型。Java虛擬機但願編譯器在編譯期間儘量的完成類型檢查,使得虛擬機在運行期間無需進行類型檢查操做。

2.4 運行時數據區域

不少人將Java的內存分爲堆內存(heap)和棧內存(Stack),這種分發不夠準確,Java的內存區域劃分實際上遠比這複雜。
Java虛擬機在執行Java程序的過程當中會把它所管理的內存劃分爲不一樣的數據區域,根據《Java虛擬機規範(Java SE7版)》的規定,這些數據區域分別爲程序計數器、Java虛擬機棧、本地方法棧、Java堆和方法區,下面咱們來一一的對它們進行介紹。

2.4.1 程序計數器

爲了保證程序可以連續地執行下去,處理器必須具備某些手段來肯定下一條指令的地址,而程序計數器正是起到這種做用。
程序計數器(Program Counter Register)也叫作PC寄存器,是一塊較小的內存空間。在虛擬機概念模型中,字節碼解釋器工做時就是經過改變程序計數器來選取下一條須要執行的字節碼指令,Java虛擬機的多線程是經過輪流切換並分配處理器執行時間的方式來實現的,在一個肯定的時刻只有一個處理器執行一條線程中的指令,爲了在線程切換後能恢復到正確的執行位置,每一個線程都會有一個獨立的程序計數器,所以,程序計數器是線程私有的。若是線程執行的方法不是Native方法,則程序計數器保存正在執行的字節碼指令地址,若是是Native方法則程序計數器的值則爲空(Undefined)。程序計數器是Java虛擬機規範中惟一沒有規定任何OutOfMemoryError狀況的數據區域。

2.4.2 Java虛擬機棧

每一條Java虛擬機線程都有一個線程私有的Java虛擬機棧(Java Virtual Machine Stacks)。它的生命週期與線程相同,與線程是同時建立的。Java虛擬機棧存儲線程中Java方法調用的狀態,包括局部變量、參數、返回值以及運算的中間結果等。一個Java虛擬機棧包含了多個棧幀,一個棧幀用來存儲局部變量表、操做數棧、動態連接、方法出口等信息。當線程調用一個Java方法時,虛擬機壓入一個新的棧幀到該線程的Java棧中,當該方法執行完成,這個棧幀就從Java棧中彈出。咱們日常所說的棧內存(Stack)指的就是Java虛擬機棧。
Java虛擬機規範中定義了兩種異常狀況:

  • 若是線程請求分配的棧容量超過Java虛擬機所容許的的最大容量,Java虛擬機會拋出StackOverflowError。
  • 若是Java虛擬機棧能夠動態擴展(大部分Java虛擬機均可以動態擴展),可是擴展時沒法申請到足夠的內存,或者在建立新的線程時沒有足夠的內存去建立對應的Java虛擬機棧,則會拋出OutOfMemoryError異常。
2.4.3 本地方法棧

Java虛擬機實現可能要用到C Stacks來支持Native語言,這個C Stacks就是本地方法棧(Native Method Stack)。它與Java虛擬機棧相似,只不過本地方法棧是用來支持Native方法服務。若是Java虛擬機不支持Native方法,而且也不依賴於C Stacks,能夠無需支持本地方法棧。在Java虛擬機規範中對本地方法棧的語言和數據結構等沒有強制規定,所以具體的Java虛擬機能夠自由實現它,好比HotSpot VM將本地方法棧和Java虛擬機棧合二爲一。
與Java虛擬機棧相似,本地方法棧也會拋出 StackOverflowError和OutOfMemoryError異常

2.4.4 Java堆

Java堆(Java Heap)是被全部線程共享的運行時內存區域。Java堆用來存放對象實例,幾乎全部的對象實例都在這裏分配內存。Java堆存儲的對象被垃圾收集器管理,這些受管理的對象無需也沒法顯示的銷燬。從內存回收的角度,Java堆能夠粗略的分爲新生代和老年代。從內存分配的角度Java堆中可能劃分出多個線程私有的分配緩衝區。無論如何劃分,Java堆存儲的內容是不變的,進行劃分是爲了能更快的回收或者分配內存。
Java堆的容量能夠時固定的,也能夠動態的擴展。Java堆的所使用的內存在物理上不須要連續,邏輯上連續便可。
Java虛擬機規範中定義了一種異常狀況:

  • 若是在堆中沒有足夠的內存來完成實例分配,而且堆也沒法進行擴展時,則會拋出OutOfMemoryError異常。
2.4.5 方法區

方法區(Method Area)是被全部線程共享的運行時內存區域。用來存儲已經被Java虛擬機加載的類的結構信息,包括:
運行時常量池、字段和方法信息、靜態變量等數據。方法區是Java堆的邏輯組成部分,它同樣在物理上不須要連續,而且能夠選擇在方法區中不實現垃圾收集。方法區並不等同於永久代,只是由於HotSpot VM使用永久代來實現方法區,對於其餘的Java虛擬機,好比J9和JRockit等,並不存在永久代概念。
Java虛擬機規範中定義了一種異常狀況:

  • 若是方法區的內存空間不知足內存分配需求時,Java虛擬機會拋出OutOfMemoryError異常。

運行時常量池
運行時常量池(Runtime Constant Pool)是方法區的一部分。在2.1 Class文件格式這一小節中咱們得知,Class文件不只包含了類的版本、接口、字段和方法等信息,還包含了常量池,它用來存放編譯時期生成的字面量和符號引用,這些內容會在類加載後存放在方法區的運行時常量池中。運行時常量池能夠理解爲是類或接口的常量池的運行時表現形式。
Java虛擬機規範中定義了一種異常狀況:
當建立類或接口時,若是構造運行時常量池所需的內存超過了方法區所能提供的最大值,Java虛擬機會拋出OutOfMemoryError異常。

參考資料
《深刻理解Java虛擬機 第二版》
《Java虛擬機規範(Java SE7版)》
理解Java虛擬機體系結構
目前主流的 Java 虛擬機有哪些?-知乎
jvm運行時數據區域解析
Java虛擬機原理圖解
深刻探討 Java 類加載器

相關文章
相關標籤/搜索