JVM系列之JVM體系(一)

rqlc8937 8年前發布 | 7K 次閱讀 JVM Java開發

來自: http://www.cnblogs.com/leesf456/p/5204694.html

前言、為什么要學習了解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虛擬機的結構框圖,主要描述了JVM子系統和內存區。

三、Java虛擬機各組成部分

3.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實例,完成連接初始化操作。

3.2 方法區

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

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

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

3.3 堆

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

3.4 程序計數器

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

3.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類型值,當被存回堆或者方法區時,才會轉化回原來的類型。

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

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

3.6 本地方法棧

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

3.7 執行引擎

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

3.8 本地方法接口

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

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

 本文由用戶 rqlc8937 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!