1. 程式人生 > >淺析Java虛擬機器結構與機制

淺析Java虛擬機器結構與機制

本文旨在給所有希望瞭解jvmjava Virtual Machine)的同學一個概念性的入門,主要介紹了JVM的組成部分以及它們內部工作的機制和原理。當然本文只是一個簡單的入門,不會涉及過多繁雜的引數和配置,感興趣的同學可以做更深入的研究,在研究JVM的過程中會發現,其實JVM本身就是一個計算機體系結構,很多原理和我們平時的硬體、微機原理、作業系統都有十分相似的地方,所以學習JVM本身也是加深自我對計算機結構認識的一個很好的途徑。

另外需要注意的是,雖然平時我們用的大多是Sun(現已被收購)JDK提供的JVM,但是JVM本身是一個規範,所以可以有多種實現,除了外,還有諸如Oracle的、IBM的

J9也都是非常有名的JVM。

一、JVM結構

下圖展示了JVM的主要結構:

可以看出,JVM主要由類載入器子系統、執行時資料區(記憶體空間)、執行引擎以及與本地方法介面等組成。其中執行時資料區又由方法區、堆、Java棧、PC暫存器、本地方法棧組成。

從上圖中還可以看出,在記憶體空間中方法區和堆是所有Java執行緒共享的,而Java棧、本地方法棧、PC暫存器則由每個執行緒私有,這會引出一些問題,後文會進行具體討論。

眾所周知,Java語言具有跨平臺的特性,這也是由JVM來實現的。更準確地說,是Sun利用JVM在不同平臺上的實現幫我們把平臺相關性的問題給解決了,這就好比是HTML語言可以在不同廠商的瀏覽器上呈現元素(雖然某些瀏覽器在對W3C標準的支援上還有一些問題)。同時,Java語言支援通過JNI(Java Native Interface)來實現本地方法的呼叫,但是需要注意到,如果你在Java程式用呼叫了本地方法,那麼你的程式就很可能不再具有跨平臺性,即本地方法會破壞平臺無關性。

二、類載入器子系統(Class Loader)

類載入器子系統負責載入編譯好的.class位元組碼檔案,並裝入記憶體,使JVM可以例項化或以其它方式使用載入後的類。JVM的類載入子系統支援在執行時的動態載入,動態載入的優點有很多,例如可以節省記憶體空間、靈活地從網路上載入類,動態載入的另一好處是可以通過名稱空間的分隔來實現類的隔離,增強了整個系統的安全性。

1、ClassLoader的分類:

a.啟動類載入器(BootStrap Class Loader):負責載入rt.jar檔案中所有的Java類,即Java的核心類都是由該ClassLoader載入。在Sun JDK中,這個類載入器是由C++實現的,並且在Java語言中無法獲得它的引用。

b.擴充套件類載入器(Extension Class Loader):負責載入一些擴充套件功能的jar包。

c.系統類載入器(System Class Loader):負責載入啟動引數中指定的Classpath中的jar包及目錄,通常我們自己寫的Java類也是由該ClassLoader載入。在Sun JDK中,系統類載入器的名字叫AppClassLoader。

d.使用者自定義類載入器(User Defined Class Loader):由使用者自定義類的載入規則,可以手動控制載入過程中的步驟。

2、ClassLoader的工作原理

類載入分為裝載、連結、初始化三步。

a.裝載

通過類的全限定名和ClassLoader載入類,主要是將指定的.class檔案載入至JVM。當類被載入以後,在JVM內部就以“類的全限定名+ClassLoader例項ID”來標明類。

在記憶體中,ClassLoader例項和類的例項都位於堆中,它們的類資訊都位於方法區。

裝載過程採用了一種被稱為“”的方式,當一個ClassLoader要載入類時,它會先請求它的雙親ClassLoader(其實這裡只有兩個ClassLoader,所以稱為父ClassLoader可能更容易理解)載入類,而它的雙親ClassLoader會繼續把載入請求提交再上一級的ClassLoader,直到啟動類載入器。只有其雙親ClassLoader無法載入指定的類時,它才會自己載入類。

雙親委派模型是JVM的第一道安全防線,它保證了類的安全載入,這裡同時依賴了類載入器隔離的原理:不同類載入器載入的類之間是無法直接互動的,即使是同一個類,被不同的ClassLoader載入,它們也無法感知到彼此的存在。這樣即使有惡意的類冒充自己在核心包(例如java.lang)下,由於它無法被啟動類載入器載入,也造成不了危害。

由此也可見,如果使用者自定義了類載入器,那就必須自己保障類載入過程中的安全。

b.連結

連結的任務是把二進位制的型別資訊合併到JVM執行時狀態中去。

連結分為以下三步:

a.驗證:校驗.class檔案的正確性,確保該檔案是符合規範定義的,並且適合當前JVM使用。

b.準備:為類分配記憶體,同時初始化類中的靜態變數賦值為預設值。

c.解析(可選):主要是把類的常量池中的符號引用解析為直接引用,這一步可以在用到相應的引用時再解析。

c.初始化

初始化類中的靜態變數,並執行類中的static程式碼、建構函式。

JVM規範嚴格定義了何時需要對類進行初始化:

a、通過new關鍵字、反射、clone、反序列化機制例項化物件時。

b、呼叫類的靜態方法時。

c、使用類的靜態欄位或對其賦值時。

d、通過反射呼叫類的方法時。

e、初始化該類的子類時(初始化子類前其父類必須已經被初始化)。

f、JVM啟動時被標記為啟動類的類(簡單理解為具有main方法的類)。

三、Java棧(Java Stack)

Java棧由棧幀組成,一個幀對應一個方法呼叫。呼叫方法時壓入棧幀,方法返回時彈出棧幀並拋棄。Java棧的主要任務是儲存方法引數、區域性變數、中間運算結果,並且提供部分其它模組工作需要的資料。前面已經提到Java棧是執行緒私有的,這就保證了執行緒安全性,使得程式設計師無需考慮棧同步訪問的問題,只有執行緒本身可以訪問它自己的區域性變數區。

它分為三部分:區域性變數區、運算元棧、幀資料區。

1、區域性變數區

區域性變數區是以字長為單位的陣列,在這裡,byte、short、char型別會被轉換成int型別儲存,除了long和double型別佔兩個字長以外,其餘型別都只佔用一個字長。特別地,boolean型別在編譯時會被轉換成int或byte型別,boolean陣列會被當做byte型別陣列來處理。區域性變數區也會包含物件的引用,包括類引用、介面引用以及陣列引用。

區域性變數區包含了方法引數和區域性變數,此外,例項方法隱含第一個區域性變數this,它指向呼叫該方法的物件引用。對於物件,區域性變數區中永遠只有指向堆的引用。

2、運算元棧

運算元棧也是以字長為單位的陣列,但是正如其名,它只能進行入棧出棧的基本操作。在進行計算時,運算元被彈出棧,計算完畢後再入棧。

3、幀資料區

幀資料區的任務主要有:

a.記錄指向類的常量池的指標,以便於解析。

b.幫助方法的正常返回,包括恢復呼叫該方法的棧幀,設定PC暫存器指向呼叫方法對應的下一條指令,把返回值壓入呼叫棧幀的運算元棧中。

c.記錄異常表,發生異常時將控制權交由對應異常的catch子句,如果沒有找到對應的catch子句,會恢復呼叫方法的棧幀並重新丟擲異常。

區域性變數區和運算元棧的大小依照具體方法在編譯時就已經確定。呼叫方法時會從方法區中找到對應類的型別資訊,從中得到具體方法的區域性變數區和運算元棧的大小,依此分配棧幀記憶體,壓入Java棧。

四、本地方法棧(Native Method Stack)

本地方法棧類似於Java棧,主要儲存了本地方法呼叫的狀態。在Sun JDK中,本地方法棧和Java棧是同一個。

五、方法區(Method Area)

型別資訊和類的靜態變數都儲存在方法區中。方法區中對於每個類儲存了以下資料:

a.類及其父類的全限定名(java.lang.Object沒有父類)

b.類的型別(Class or Interface)

c.訪問修飾符(public, abstract, final)

d.實現的介面的全限定名的列表

e.常量池

f.欄位資訊

g.方法資訊

h.靜態變數

i.ClassLoader引用

j.Class引用

可見類的所有資訊都儲存在方法區中。由於方法區是所有執行緒共享的,所以必須保證執行緒安全,舉例來說,如果兩個類同時要載入一個尚未被載入的類,那麼一個類會請求它的ClassLoader去載入需要的類,另一個類只能等待而不會重複載入。

此外為了加快呼叫方法的速度,通常還會為每個非抽象類建立私有的方法表,方法表是一個數組,存放了例項可能被呼叫的例項方法的直接引用。方法表對於多型有非常重要的意義,具體可以參照《》一文中“多型的實現”一節。

在Sun JDK中,方法區對應了持久代(Permanent Generation),預設最小值為16MB,最大值為64MB。

六、堆(Heap)

堆用於儲存物件例項以及陣列值。堆中有指向類資料的指標,該指標指向了方法區中對應的型別資訊。堆中還可能存放了指向方法表的指標。堆是所有執行緒共享的,所以在進行例項化物件等操作時,需要解決同步問題。此外,堆中的例項資料中還包含了物件鎖,並且針對不同的垃圾收集策略,可能存放了引用計數或清掃標記等資料。

在堆的管理上,Sun JDK從1.2版本開始引入了分代管理的方式。主要分為新生代、舊生代。分代方式大大改善了垃圾收集的效率。

1、新生代(New Generation)

大多數情況下新物件都被分配在新生代中,新生代由Eden Space和兩塊相同大小的Survivor Space組成,後兩者主要用於Minor GC時的物件複製(Minor GC的過程在此不詳細討論)。

JVM在Eden Space中會開闢一小塊獨立的TLAB(Thread Local Allocation Buffer)區域用於更高效的記憶體分配,我們知道在堆上分配記憶體需要鎖定整個堆,而在TLAB上則不需要,JVM在分配物件時會盡量在TLAB上分配,以提高效率。

2、舊生代(Old Generation/Tenuring Generation)

在新生代中存活時間較久的物件將會被轉入舊生代,舊生代進行垃圾收集的頻率沒有新生代高。

七、執行引擎

執行引擎是JVM執行Java位元組碼的核心,執行方式主要分為解釋執行、編譯執行、自適應優化執行、硬體晶片執行方式。

JVM的指令集是基於棧而非暫存器的,這樣做的好處在於可以使指令儘可能緊湊,便於快速地在網路上傳輸(別忘了Java最初就是為網路設計的),同時也很容易適應通用暫存器較少的平臺,並且有利於程式碼優化,由於Java棧和PC暫存器是執行緒私有的,執行緒之間無法互相干涉彼此的棧。每個執行緒擁有獨立的JVM執行引擎例項。

JVM指令由單位元組操作碼和若干運算元組成。對於需要運算元的指令,通常是先把運算元壓入運算元棧,即使是對區域性變數賦值,也會先入棧再賦值。注意這裡是“通常”情況,之後會講到由於優化導致的例外。

1、解釋執行

和一些動態語言類似,JVM可以解釋執行位元組碼。Sun JDK採用了的方式,感興趣的同學可以深入瞭解一下。

解釋執行中有幾種優化方式:

a.棧頂快取

將位於運算元棧頂的值直接快取在暫存器上,對於大部分只需要一個運算元的指令而言,就無需再入棧,可以直接在暫存器上進行計算,結果壓入運算元站。這樣便減少了暫存器和記憶體的交換開銷。

b.部分棧幀共享

被呼叫方法可將呼叫方法棧幀中的運算元棧作為自己的區域性變數區,這樣在獲取方法引數時減少了複製引數的開銷。

c.執行機器指令

在一些特殊情況下,JVM會執行機器指令以提高速度。

2、編譯執行

為了提升執行速度,Sun JDK提供了將位元組碼編譯為機器指令的支援,主要利用了JIT(Just-In-Time)編譯器在執行時進行編譯,它會在第一次執行時編譯位元組碼為機器碼並快取,之後就可以重複利用。Oracle JRockit採用的是完全的編譯執行。

3、自適應優化執行

自適應優化執行的思想是程式中10%~20%的程式碼佔據了80%~90%的執行時間,所以通過將那少部分程式碼編譯為優化過的機器碼就可以大大提升執行效率。自適應優化的典型代表是Sun的Hotspot VM,正如其名,JVM會監測程式碼的執行情況,當判斷特定方法是瓶頸或熱點時,將會啟動一個後臺執行緒,把該方法的位元組碼編譯為極度優化的、靜態連結的C++程式碼。當方法不再是熱區時,則會取消編譯過的程式碼,重新進行解釋執行。

自適應優化不僅通過利用小部分的編譯時間獲得大部分的效率提升,而且由於在執行過程中時刻監測,對內聯程式碼等優化也起到了很大的作用。由於面向物件的多型性,一個方法可能對應了很多種不同實現,自適應優化就可以通過監測只內聯那些用到的程式碼,大大減少了行內函數的大小。

Sun JDK在編譯上採用了兩種模式:Client和Server模式。前者較為輕量級,佔用記憶體較少。後者的優化程式更高,佔用記憶體更多。

在Server模式中會進行物件的逃逸分析,即方法中的物件是否會在方法外使用,如果被其它方法使用了,則該物件是逃逸的。對於非逃逸物件,JVM會在棧上直接分配物件(所以物件不一定是在堆上分配的),執行緒獲取物件會更加快速,同時當方法返回時,由於棧幀被拋棄,也有利於物件的垃圾收集。Server模式還會通過分析去除一些不必要的同步,感興趣的同學可以研究一下Sun JDK 6引入的機制。

此外,執行引擎也必須保證執行緒安全性,因而JMM(Java Memory Model)也是由執行引擎確保的。

本文發表自,轉自hesey

相關推薦

淺析Java虛擬機器結構機制

本文旨在給所有希望瞭解jvm(java Virtual Machine)的同學一個概念性的入門,主要介紹了JVM的組成部分以及它們內部工作的機制和原理。當然本文只是一個簡單的入門,不會涉及過多繁雜的引數和配置,感興趣的同學可以做更深入的研究,在研究JVM的過程中會發現,

Java淺析虛擬機器結構機制

 淺析Java虛擬機器結構與機制        本文旨在給所有希望瞭解JVM(Java Virtual Machine)的同學一個概念性的入門,主要介紹了JVM的組成部分以及它們內部工作的機制和原理。當然本文只是一個簡單的入門,不會涉及過多繁雜的引數和配置,感興趣的同學

Java虛擬機器記憶體分配機制啟動引數說明

-Xms :表示java虛擬機器堆區記憶體初始記憶體分配的大小,通常為作業系統可用記憶體的1/64大小即可,但仍需按照實際情況進行分配。-Xmx: 表示java虛擬機器堆區記憶體可被分配的最大上限,通常為作業系統可用記憶體的1/4大小。但是開發過程中,通常會將 -Xms 與 -Xmx兩個引數的配置相同的值,其

JAVA虛擬機器結構之執行時資料區

jvm的執行時資料區根據用途一共可以分為這幾類:pc寄存機,java虛擬機器棧,java堆,方法區,執行時常量池,本地方法棧。其中java堆,方法區,執行時常量是公有的資料區,隨著虛擬機器的啟動而建立,隨著虛擬的退出而銷燬。而pc暫存器,java虛擬機器棧,本地方法棧則是執行緒私有的

java虛擬機器類載入機制學習

1、什麼是類的載入 類的載入指的是將類的.class檔案中的二進位制資料讀入到記憶體中,將其放在執行時資料區的方法區內,然後在堆區建立一個java.lang.Class物件,用來封裝類在方法區內的資料結構。類的載入的最終產品是位於堆區中的Class物件,Class物件封裝了類在方法區內的資料結構

Java虛擬機器類載入機制經典案例

package io.lgxkdream.test; class Father { static Father f = new Father(); static { System.out.println("father-1"); } { System.out.println("

java虛擬機器本地方法棧

java虛擬機器棧:    虛擬機器棧描述的是java方法執行的記憶體模型,每個方法在執行的同事都會建立一個棧幀用於儲存區域性變量表、運算元棧、動態連結,方法出口燈資訊。每一個方法從呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中入棧到出棧的過程。 在java虛擬機

jdk原始碼解析(七)——Java虛擬機器類載入機制

前面我們講解了class檔案的格式,以及它是什麼樣的。那麼接下來需要了解它怎麼被載入到jvm中呢?jvm的載入機制又是怎麼一個過程呢?本文參考了《Java 虛擬機器規範(Java SE 7 版)》的第五章內容來詳細解釋一下 虛擬機器類載入機制:虛擬機器把描述類的資料從cla

Java虛擬機器結構詳解

1   JVM整體架構 2   JVM類載入器 3   JVM記憶體結構 4   JVM執行引擎 1  JVM整體架構 •   JVM(虛擬機器):指以軟體的方式模擬具有完整硬體系統功能、執行在一個完全隔離環境中的完整計算機系統 ,是物理機的軟體實   現。常用的虛

java虛擬機器結構詳解(JVM)

廢話不多說,先直接上個圖: 上圖為JVM整體組成結構,有幾個模組組成: 1.class檔案生成模組: 通過jdk自帶的javac編譯命令生成 中間過程就是javac編譯程式內部處理的過程,核心就是針對原始碼詞法和語法的分析。 2.類載入器子系統模組: JVM執行時

Java 虛擬機器類載入機制

看到這個題目,很多人會覺得我寫我的java程式碼,至於類,JVM愛怎麼載入就怎麼載入,博主有很長一段時間也是這麼認為的。隨著程式設計經驗的日積月累,越來越感覺到了解虛擬機器相關要領的重要性。閒話不多說,老規矩,先來一段程式碼吊吊胃口。public class SSClass{

Java虛擬機器類載入機制

原文出處:http://www.importnew.com/18548.html類載入過程類從被載入到虛擬機器記憶體中開始,到卸載出記憶體為止,它的整個生命週期包括:載入(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolut

java虛擬機器記憶體管理機制(一):JVM記憶體管理總結【分享】

近期看了看Java記憶體洩露的一些案例,跟原來的幾個哥們討論了一下,深入研究發現JVM裡面還是有不少以前不知道的細節,這裡稍微剖析一下。先看一看JVM的內部結構——如圖所示,JVM主要包括兩個子系統和兩個元件。兩個子系統分別是Class loader子系統和Execution

Java虛擬機器結構(記憶體,類載入器,執行引擎)

1. JVM背景知識 1995年,Java誕生於Sun公司。目標:Write Once, Run Anywhere。 2006年,Sun宣佈Java開源,並在隨後1年,陸續將JDK的各部分在GPL v2協議下公開原始碼,並建立OpenJDK組織,對原始

老生常談Java虛擬機器垃圾回收機制(必看篇)

二、垃圾收集 垃圾收集主要是針對堆和方法區進行。 程式計數器、虛擬機器棧和本地方法棧這三個區域屬於執行緒私有的,只存在於執行

每日一問:你瞭解 Java 虛擬機器結構麼?

對於從事 C/C++ 程式設計師開發的小夥伴來說,在記憶體管理領域非常頭疼,因為他們總是需要對每一個 new 操作去寫配對的 delete/free 程式碼。而對於我們 Android 乃至 Java 程式設計師,卻總是會因為虛擬機器的自動記憶體管理機制而忽視記憶體管理的重要性。 經過前面簡短的幾篇純 And

Java 虛擬機器結構

一 資料型別 與 Java 程式語言中的資料型別相似,Java 虛擬機器可以操作的資料型別可分為兩類:原始型別(Primitive Types,也經常翻譯為原生型別或者基本型別)和引用型別(Reference Types)。 與之對應,也存在有原始值(Primitive Values)和引用值(Referen

JVM(三)-java虛擬機器類載入機制

概述:   上一篇文章,介紹了java虛擬機器的執行時區域,Java虛擬機器根據不同的分工,把記憶體劃分為各個不同的區域。在java程式中,最小的執行單元一般都是建立一個物件,然後呼叫物件的某個 方法。通過上一篇文章我們知道呼叫某個方法是通過虛擬機器棧的棧幀並通過執行引擎來實現的,但是實際上一個方法的執行前提

Java 虛擬機器垃圾收集機制詳解

> 本文摘自深入理解 Java 虛擬機器第三版 ## 垃圾收集發生的區域 之前我們介紹過 Java 記憶體執行時區域的各個部分,其中程式計數器、虛擬機器棧、本地方法棧三個區域隨執行緒共存亡。棧中的每一個棧幀分配多少記憶體基本上在類結構確定下來時就已知,因此這幾個區域的記憶體分配和回收都具有確定性,

深入理解Java虛擬機器(類檔案結構+類載入機制+位元組碼執行引擎)

周志明的《深入理解Java虛擬機器》很好很強大,閱讀起來頗有點費勁,尤其是當你跟隨作者的思路一直探究下去,開始會讓你弄不清方向,難免有些你說的啥子的感覺。但知識不得不學,於是天天看,反覆看,就慢慢的理解了。我其實不想說這種硬磨的方法有多好,我甚至不推薦,我建議大家閱讀這本書時,由淺入深,有舍有得,先從巨集觀去