【JVM】JVM系列之JVM體系(一)


一、前言

  為什么要學習了解Java虛擬機

  1.我們需要更加清楚的了解Java底層是如何運作的,有利於我們更深刻的學習好Java。

  2.對我們調試錯誤提供很寶貴的經驗。

  3.這是合格的Java程序必須要了解的內容。

  基於此,筆者打算出一個Java虛擬機的系列,加深自己對知識點的理解,同時也方便各位有需要的園友。

二、Java虛擬機的定義

  Java虛擬機(Java Virtual Machine),簡稱JVM。當我們說起Java虛擬機時,可能指的是如下三種不同的東西:

    1.抽象規范。

    2.一個具體的實現。

    3.一個運行中的虛擬機實例。

  Java虛擬機抽象規范僅僅是一個概念,在《The Java Virtual Machine Specification》中有詳細的描述。該規范的實現,可能來自多個提供商,並存在於多個平台上,它或者是全部由軟件實現,或者是以硬件和軟件相結合的方式來實現。當運行一個Java程序的時候,也就在運行一個Java虛擬機實例。注意,我們所說的Java平台無關性是指class文件的平台無關性,JVM是和平台相關的,不同操作系統對應不同的JVM。

三、Java虛擬機的總體框架圖

  下圖是整個Java虛擬機的總體框架圖,之后我們會經常涉及到。

四、Java虛擬機的體系結構

  下圖表示了Java虛擬機的結構框圖,主要描述了JVM子系統和內存區。

  

五、Java虛擬機各組成部分

  5.1 類裝載子系統

  類裝載子系統負責查找並裝載類型,Java虛擬機由兩種類裝載器:啟動類裝載器(Java虛擬機實現的一部分)和用戶自定義類裝載器(Java程序的一部分)。類裝載子系統負責定位和導入二進制class文件,並且保證導入類的正確性,為類變量分配並初始化內存,以及幫助解析符號引用。類裝載器必須嚴格按照如下順序進行類的裝載。

  1) 裝載 -- 查找並裝載類型的二進制數據

  2) 連接 -- 執行驗證,准備,以及解析(可選),連接分為如下三個步驟

    驗證 -- 確保被導入類型的正確性

    准備 -- 為類變量分配內存,並將其初始化為默認值

    解析 -- 把類型中的符號引用轉換為直接引用

  3) 初始化 -- 把類變量初始化為正確初始值

  啟動類裝載器 -- Java虛擬機必須有一個啟動類裝載器,用於裝載受信任的類,如Java API的class文件。

  用戶自定義類裝載器 -- 繼承自ClassLoader類,ClassLoader的如下四個方法,是通往Java虛擬機的通道。

    1. protected final Class defineClass(String name, byte data[], int offset, int length);

    2. protected final Class defineClass(String name, byte data[], int offset, int length, ProtectionDomain protectionDomain);

    3. protected final Class findSystemClass(String name);

    4. protected final void resolveClass(Class c);

  這四個方法涉及到了裝載和連接兩個階段,defineClass方法data參數為二進制Java Class文件格式,表示一個新的可用類型,之后把這個類型導入到方法區中。findSystemClass的參數為全限定名,通過類裝載器進行裝載。resolveClass參數為Class實例,完成連接初始化操作。

  5.2 方法區

  方法區是線程共享的內存區域,用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據,當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError。

  類信息包括1.類型全限定名。2.類型的直接超類的全限定名(除非這個類型是java.lang.Object,它沒有超類)。3.類型是類類型還是接口類型。4.類型的訪問修飾符(public、abstract、final的某個子集)。5.任何直接超接口的全限定名的有序列表。6.類型的常量池。7.字段信息。8.方法信息。9.除了常量以外的所有類(靜態)變量。10.一個到類ClassLoader的引用。11.一個到Class類的引用。

  着重介紹常量池 -- 虛擬機必須要為每個被裝載的類型維護一個常量池。常量池就是該類型所用常量的一個有序集合,包括直接常量和對其他類型、字段和方法的符號引用。它在Java程序的動態連接中起着核心作用。

  5.3 堆

  一個虛擬機實例只對應一個堆空間,堆是線程共享的。堆空間是存放對象實例的地方,幾乎所有對象實例都在這里分配。堆也是垃圾收集器管理的主要區域。堆可以處於物理上不連續的內存空間中,只要邏輯上相連就行,當堆中沒有足夠的內存進行對象實例分配時,並且堆也無法擴展時,會拋出OutOfMemoryError異常。

  5.4 程序計數器

  每個線程擁有自己的程序計數器,線程之間的程序計數器互不影響。PC寄存器的內容總是下一條將被執行指令的"地址",這里的"地址"可以是一個本地指針,也可以是在方法字節碼中相對於該方法起始指令的偏移量。如果該線程正在執行一個本地方法,則程序計數器內容為undefined,此區域在Java虛擬機規范中沒有規定任何OutOfMemoryError情況的區域。

  5.5 Java棧

  Java棧也是線程私有的,虛擬機只會對棧進行兩種操作,以幀為單位的入棧和出棧。每個方法在執行時都會創建一個幀,並入棧,成為當前幀。棧幀由三部分組成:局部變量區、操作數棧、幀數據區。

  局部變量區被組織為一個以字長為單位、從0開始計數的數組。字節碼指令通過從0開始的索引來使用其中的數據。類型為int、float、reference和return Address的值在數組中只占據一項,而類型為byte、short和char的值存入時都會轉化為int類型,也占一項,而long、double則連續占據兩項。

  關於局部變量區給出如下一個例子。

public class Example {
public static int classMethod(int i, long l, float f, double d, Object o, byte b) {
return 0;
}

public int instanceMethod(char c, double d, short s, boolean b) {
return 0;
}
}
View Code

  

  可以看到類方法的首項中沒有隱含的this指針,而對象方法則會隱含this指針。並且byte,char,short,boolean類型存入局部變量區的時候都會被轉化成int類型值,當被存回堆或者方法區時,才會轉化回原來的類型。

  操作數棧被組織成一個以字長為單位的數組,它是通過標准的棧操作-入棧和出棧來進行訪問,而不是通過索引訪問。入棧和出棧也會存在類型的轉化。

  棧數據區存放一些用於支持常量池解析、正常方法返回以及異常派發機制的信息。即將常量池的符號引用轉化為直接地址引用、恢復發起調用的方法的幀進行正常返回,發生異常時轉交異常表進行處理。

  5.6 本地方法棧

  訪問本地方式時使用到的棧,為本地方法服務,本地方法區域也會拋出StackOverflowError和OutOfMemoryError異常。

  5.7 執行引擎

  用戶所編寫的程序如何表現正確的行為需要執行引擎的支持,執行引擎執行字節碼指令,完成程序的功能。后面會詳細介紹。

  5.8 本地方法接口

  本地方法接口稱為JNI,是為可移植性准備的。

六、總結

  至此,虛擬機的結構就已經大體介紹完了,現在我們只需要有一個初步的了解,后面對各個部分會有更加詳細的介紹。謝謝各位園友觀看~

      


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com