JVM系列之JVM體系(一)
來自: 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,是為可移植性準備的。
至此,虛擬機的結構就已經大體介紹完了,現在我們只需要有一個初步的了解,后面對各個部分會有更加詳細的介紹。