本文主要對(duì)Java程序的執(zhí)行模式和JVM的架構(gòu)原理進(jìn)行較易理解的介紹和剖析,以便能更好的掌握J(rèn)ava的核心機(jī)制和基本原理,拋磚引玉,以便引起Java愛(ài)好這的興趣。如果覺(jué)得有用,請(qǐng)點(diǎn)個(gè)贊,順手分享本文。Thanks a lot. 一、Java程序的兩個(gè)環(huán)境所謂Java程序,即用Java語(yǔ)言編寫(xiě)的程序,它包含數(shù)據(jù)、代碼以及相關(guān)算法。而一個(gè)有效的java程序,滿(mǎn)足兩個(gè)環(huán)境的要求,即編譯環(huán)境和運(yùn)行環(huán)境。如下圖所示: 圖-1:Java程序運(yùn)行環(huán) 根據(jù)上圖所示: 其一,在編譯環(huán)境中,我們基于Java語(yǔ)言和JDK(Java開(kāi)發(fā)工具包),進(jìn)行源程序的代碼編寫(xiě),并在確保正確的情況下,通過(guò)工具包提供編譯器,把所有源代碼(即.java)編譯成(通過(guò)javac命令實(shí)現(xiàn))字節(jié)碼文件(即.class文件)。 其二,在運(yùn)行時(shí)環(huán)境執(zhí)行程序,或說(shuō)運(yùn)行程序。這時(shí),需要先擁有待運(yùn)行程序的字節(jié)碼文件。這些字節(jié)碼文件有可能通過(guò)網(wǎng)絡(luò)或者在本地兩種方式傳遞到運(yùn)行時(shí)環(huán)境。 運(yùn)行時(shí)環(huán)境中主要的工作就是啟動(dòng)Java虛擬機(jī),并通過(guò)虛擬機(jī)來(lái)完成一系列工作,實(shí)現(xiàn)java程序的運(yùn)行。需要注意的是,在java虛擬執(zhí)行程序時(shí),它會(huì)根據(jù)需要來(lái)加載Java提供的相關(guān)API的class文件。 二、Java的JVM運(yùn)行結(jié)構(gòu) 基于上面的java程序運(yùn)行的框架圖,我們進(jìn)一步來(lái)透視java的核心基石,即java虛擬機(jī)JVM的內(nèi)部運(yùn)行組成。 根據(jù)Java的虛擬機(jī)規(guī)范,JVM內(nèi)部抽象體系結(jié)構(gòu)主要有這樣幾大部分組成,即類(lèi)裝載器子系統(tǒng)、執(zhí)行引擎以及運(yùn)行時(shí)數(shù)據(jù)管理區(qū),同時(shí)要求支持本地方法的調(diào)用機(jī)制。那么這樣一來(lái),我們進(jìn)一步細(xì)化Java程序的JVM內(nèi)部執(zhí)行機(jī)制,就形成如下的Java運(yùn)行模式架構(gòu): 圖-2:JVM運(yùn)行流程結(jié)構(gòu)圖 三、JVM的架構(gòu)原理和運(yùn)行機(jī)制 經(jīng)過(guò)上一部分的內(nèi)容的抽象和總結(jié),那么我們可以進(jìn)一步抽象出基于java虛擬機(jī)規(guī)范實(shí)現(xiàn)的一般的JVM實(shí)現(xiàn)組成架構(gòu)以及其運(yùn)行機(jī)制和原理。JVM詳細(xì)的參考架構(gòu)圖如下: 圖-3:虛擬機(jī)JVM參考實(shí)現(xiàn)圖 針對(duì)上圖JVM參考架構(gòu),作簡(jiǎn)要說(shuō)明如下: 虛擬機(jī)JVM主要有三個(gè)子系統(tǒng)構(gòu)成: 1-類(lèi)裝入器子系統(tǒng) 2-運(yùn)行時(shí)數(shù)據(jù)區(qū) 3-執(zhí)行引擎 1.類(lèi)裝入器子系統(tǒng) Java的動(dòng)態(tài)類(lèi)加載功能是由類(lèi)裝入器子系統(tǒng)。 由他進(jìn)行類(lèi)的裝載、鏈接、并初始化類(lèi)文件時(shí),是指一個(gè)類(lèi)第一次運(yùn)行時(shí),而不是編譯時(shí)間。 1.1類(lèi)加載 1)Bootstrap類(lèi)加載器 負(fù)責(zé)加載$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++實(shí)現(xiàn),不是ClassLoader子類(lèi) 2)Extension類(lèi)加載器 負(fù)責(zé)加載java平臺(tái)中擴(kuò)展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包 3)Application類(lèi)加載器 負(fù)責(zé)記載classpath中指定的jar包及目錄中class 4)Custom類(lèi)加載器 屬于應(yīng)用程序根據(jù)自身需要自定義的ClassLoader,如tomcat、jboss都會(huì)根據(jù)j2ee規(guī)范自行實(shí)現(xiàn)ClassLoader。加載過(guò)程中會(huì)先檢查類(lèi)是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個(gè)classloader已加載就視為已加載此類(lèi),保證此類(lèi)只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來(lái)逐層嘗試加載此類(lèi)。 1.2連接 驗(yàn)證——字節(jié)碼校驗(yàn)器會(huì)檢查生成的字節(jié)碼是否正確,如果驗(yàn)證失敗則會(huì)驗(yàn)證錯(cuò)誤。 準(zhǔn)備——對(duì)于所有靜態(tài)變量的內(nèi)存分配和默認(rèn)值分配。 識(shí)別——解析或識(shí)別是從運(yùn)行時(shí)常量池的符號(hào)引用中動(dòng)態(tài)具體值的過(guò)程。 1.3初始化 這是類(lèi)裝入的最后階段, 類(lèi)或接口的初始化由執(zhí)行類(lèi)或接口初始化方法構(gòu)成。這里所有的靜態(tài)變量與原來(lái)的值將被指派,靜態(tài)塊將被執(zhí)行。 2.運(yùn)行時(shí)數(shù)據(jù)區(qū) 運(yùn)行時(shí)數(shù)據(jù)區(qū)域分為5個(gè)主要組件: 方法區(qū)——所有的類(lèi)級(jí)別的數(shù)據(jù)將存儲(chǔ)在這里,包括靜態(tài)變量。 每個(gè)JVM區(qū)域只有一個(gè)方法,它是一個(gè)共享資源。一般會(huì)包含一個(gè)運(yùn)行時(shí)常量池(運(yùn)行時(shí)常量池:一個(gè)存儲(chǔ)了類(lèi)文件格式中的常量池表的內(nèi)存空間。這部分空間雖然存在于方法區(qū)內(nèi),但卻在JVM操作中扮演著舉足輕重的角色,因此JVM規(guī)范單獨(dú)把這一部分拿出來(lái)描述。除了每個(gè)類(lèi)或接口中定義的常量,它還包含了所有對(duì)方法和字段的引用。因此當(dāng)需要一個(gè)方法或字段時(shí),JVM通過(guò)運(yùn)行時(shí)常量池中的信息從內(nèi)存空間中來(lái)查找其相應(yīng)的實(shí)際地址)。 堆區(qū)域——所有的對(duì)象和相應(yīng)的實(shí)例變量和數(shù)組將存儲(chǔ)在這里。 還有一堆區(qū)域每個(gè)JVM。 自方法和堆區(qū)域多個(gè)線程共享內(nèi)存,存儲(chǔ)的數(shù)據(jù)不是線程安全的。 棧區(qū)域——每一個(gè)線程創(chuàng)建一個(gè)單獨(dú)的運(yùn)行時(shí)堆棧。 對(duì)于每一個(gè)方法調(diào)用,一個(gè)稱(chēng)為棧內(nèi)存棧幀被創(chuàng)建。 所有局部變量將被創(chuàng)建在棧內(nèi)存中。 棧區(qū)域是線程安全的,因?yàn)樗皇且粋€(gè)共享資源。 棧幀分三個(gè)實(shí)體: 其一,局部變量數(shù)組——有多少相關(guān)的方法局部變量以及相應(yīng)的值將被存儲(chǔ)在這里。 其二,操作數(shù)棧——如果任何中間操作要求執(zhí)行,操作數(shù)棧作為運(yùn)行時(shí)工作區(qū)執(zhí)行操作。 其三,幀數(shù)據(jù)——所有的符號(hào)對(duì)應(yīng)的方法存儲(chǔ)在這里。 在任何的情況下異常catch塊信息將保存在幀數(shù)據(jù)。 程序計(jì)數(shù)器——每個(gè)線程必須分開(kāi)程序計(jì)數(shù)器登記,當(dāng)前執(zhí)行的指令一旦執(zhí)行,程序計(jì)數(shù)器(程序計(jì)數(shù)登記器)更新下一個(gè)指令。 本地方法棧——本地方法棧保存本機(jī)方法的信息。 為每一個(gè)線程將創(chuàng)建一個(gè)單獨(dú)的本地方法棧,以備不時(shí)之用。 3.執(zhí)行引擎 通過(guò)類(lèi)裝載器裝載的,被分配到JVM的運(yùn)行時(shí)數(shù)據(jù)區(qū)的字節(jié)碼會(huì)被執(zhí)行引擎執(zhí)行。執(zhí)行引擎以指令為單位讀取Java字節(jié)碼。它就像一個(gè)CPU一樣,一條一條地執(zhí)行機(jī)器指令。每個(gè)字節(jié)碼指令都由一個(gè)1字節(jié)的操作碼和附加的操作數(shù)組成。執(zhí)行引擎取得一個(gè)操作碼,然后根據(jù)操作數(shù)來(lái)執(zhí)行任務(wù),完成后就繼續(xù)執(zhí)行下一條操作碼。如下圖所示: 不過(guò)Java字節(jié)碼是用一種人類(lèi)可以讀懂的語(yǔ)言編寫(xiě)的,而不是用機(jī)器可以直接執(zhí)行的語(yǔ)言。因此,執(zhí)行引擎必須把字節(jié)碼轉(zhuǎn)換成可以直接被JVM執(zhí)行的語(yǔ)言。字節(jié)碼可以通過(guò)以下兩種方式轉(zhuǎn)換成合適的語(yǔ)言。執(zhí)行引擎主要包括3部分內(nèi)容: (1)解釋器:一條一條地讀取,解釋并且執(zhí)行字節(jié)碼指令。因?yàn)樗粭l一條地解釋和執(zhí)行指令,所以它可以很快地解釋字節(jié)碼,但是執(zhí)行起來(lái)會(huì)比較慢。這是解釋執(zhí)行的語(yǔ)言的一個(gè)缺點(diǎn)。字節(jié)碼這種“語(yǔ)言”基本來(lái)說(shuō)是解釋執(zhí)行的。 (2)即時(shí)(Just-In-Time)編譯器:即時(shí)編譯器被引入用來(lái)彌補(bǔ)解釋器的缺點(diǎn)。執(zhí)行引擎首先按照解釋執(zhí)行的方式來(lái)執(zhí)行,然后在合適的時(shí)候,即時(shí)編譯器把整段字節(jié)碼編譯成本地代碼。然后,執(zhí)行引擎就沒(méi)有必要再去解釋執(zhí)行方法了,它可以直接通過(guò)本地代碼去執(zhí)行它。執(zhí)行本地代碼比一條一條進(jìn)行解釋執(zhí)行的速度快很多。編譯后的代碼可以執(zhí)行的很快,因?yàn)楸镜卮a是保存在緩存里的。 不過(guò),用JIT編譯器來(lái)編譯代碼所花的時(shí)間要比用解釋器去一條條解釋執(zhí)行花的時(shí)間要多。因此,如果代碼只被執(zhí)行一次的話,那么最好還是解釋執(zhí)行而不是編譯后再執(zhí)行。因此,內(nèi)置了JIT編譯器的JVM都會(huì)檢查方法的執(zhí)行頻率,如果一個(gè)方法的執(zhí)行頻率超過(guò)一個(gè)特定的值的話,那么這個(gè)方法就會(huì)被編譯成本地代碼。JIT中不要構(gòu)成如下: 2中間代碼生成器(Intermediate Code Generator):生成中間代碼 2代碼優(yōu)化器(Code Optimizer):負(fù)責(zé)優(yōu)化上面生成的中間代碼 2目標(biāo)代碼生成器(Target Code Generator):負(fù)責(zé)生成機(jī)器代碼或本地代碼 2分析器(Profiler):一個(gè)特殊組件,負(fù)責(zé)查找熱點(diǎn),即該方法是否被多次調(diào)用; 可以簡(jiǎn)單這樣理解,JIT編譯器通過(guò)中間代碼生成器生成中間代碼,再通過(guò)代碼優(yōu)化器負(fù)責(zé)優(yōu)化生成中間代碼,最后由目標(biāo)代碼生成器負(fù)責(zé)生成機(jī)器代碼或本機(jī)代碼。在這過(guò)程中,JIT的分析器,一個(gè)特殊的組件,負(fù)責(zé)尋找熱點(diǎn),即是否多次調(diào)用的方法,再執(zhí)行上述操作。 (3)垃圾收集器(Garbage Collector):收集和刪除未引用的對(duì)象??梢酝ㄟ^(guò)調(diào)用System.gc()觸發(fā)垃圾收集,但不能保證執(zhí)行。JVM的垃圾回收對(duì)象是已創(chuàng)建的對(duì)象。 另外,Java Native Interface(JNI): JNI將與本機(jī)方法庫(kù)進(jìn)行交互,并提供執(zhí)行引擎所需的本機(jī)庫(kù)。本地方法庫(kù)(Native Method Libraries)是執(zhí)行引擎所需的本機(jī)庫(kù)的集合。 (文末)——謝謝閱讀,希望對(duì)你有所幫助。別忘了,點(diǎn)個(gè)贊、分享本文,并關(guān)注本頭條號(hào)喲~ |
|