《Java 虛擬機(jī)原理》1.1 什么是Java虛擬機(jī)
時(shí)間:2023-07-15 01:12:01 | 來源:網(wǎng)站運(yùn)營
時(shí)間:2023-07-15 01:12:01 來源:網(wǎng)站運(yùn)營
《Java 虛擬機(jī)原理》1.1 什么是Java虛擬機(jī):
一、Java虛擬機(jī)Java 虛擬機(jī)(JVM)是運(yùn)行 Java 字節(jié)碼的虛擬機(jī)。JVM 有針對(duì)不同系統(tǒng)的特定實(shí)現(xiàn)(Windows,Linux,macOS),目的是使用相同的字節(jié)碼,它們都會(huì)給出相同的結(jié)果。字節(jié)碼和不同系統(tǒng)的 JVM 實(shí)現(xiàn)是 Java 語言“
一次編譯到處運(yùn)行”的關(guān)鍵。
什么是字節(jié)碼?采用字節(jié)碼的好處是什么?字節(jié)碼(即擴(kuò)展名為 .class 的文件) 是JVM 可以理解的代碼,它不面向任何特定的處理器,只面向虛擬機(jī)。Java 語言通過字節(jié)碼的方式,在一定程度上解決了傳統(tǒng)解釋型語言執(zhí)行效率低的問題,同時(shí)又保留了解釋型語言可移植的特點(diǎn)。因此,Java 程序無須重新編譯便可在多種不同操作系統(tǒng)的計(jì)算機(jī)上運(yùn)行。
Java 程序從源代碼到運(yùn)行一般有下面 3 步:java運(yùn)行流程
字節(jié)碼=》機(jī)器碼 :在這一步 JVM 類加載器首先加載字節(jié)碼文件,然后通過解釋器逐行解釋執(zhí)行,這種方式的執(zhí)行速度會(huì)相對(duì)比較慢。同時(shí),有些方法和代碼塊是經(jīng)常需要被調(diào)用的(也就是所謂的熱點(diǎn)代碼),所以后面引進(jìn)了
JIT 編譯器,而 JIT 屬于運(yùn)行時(shí)編譯。
當(dāng) JIT 編譯器完成第一次編譯后,其會(huì)將字節(jié)碼對(duì)應(yīng)的機(jī)器碼保存下來,下次可以直接使用。而我們知道,機(jī)器碼的運(yùn)行效率肯定是高于 Java 解釋器的。這也解釋了我們?yōu)槭裁唇?jīng)常會(huì)說 Java 是編譯與解釋共存的語言。
二、Java編譯運(yùn)行的總流程java語言是可以在各種操作系統(tǒng)中運(yùn)行,即“
一次編譯,到處運(yùn)行”(Write once, run anywhere)。還有另外一個(gè)特點(diǎn):垃圾收集,即自動(dòng)分配和回收內(nèi)存。
如何實(shí)現(xiàn)“
一次編譯,到處運(yùn)行”?
總體流程:1.java源代碼被javac編譯器編譯成class二進(jìn)制字節(jié)碼文件;2.class被jvm加載到內(nèi)存中;3.字節(jié)碼執(zhí)行引擎將class轉(zhuǎn)換為可以直接運(yùn)行的機(jī)器碼。
圖1 總體流程
1.字節(jié)碼生成通過上面的簡述我們已經(jīng)知道,java語言中是用過javac程序?qū)ava文件編譯成class文件的,編譯的過程大約分為如下四步:
(1)詞法分析器:將代碼(字符流)中關(guān)鍵字和標(biāo)識(shí)符等內(nèi)容解析轉(zhuǎn)換成Token流,token流就是一組對(duì)應(yīng)源碼字符集和的單詞序列;
(2)語法分析器:解析Token流中package關(guān)鍵字聲明、import關(guān)鍵字聲明和class主體信息,組建成更加結(jié)構(gòu)話的語法樹;
(3)語義分析器:對(duì)語法樹進(jìn)一步處理,包括添加默認(rèn)構(gòu)造函數(shù)、檢查變量初始化、常量合并處理、檢查操作變量類型是否匹配、檢查操作語句是否可達(dá)、異常是否捕獲或拋出、接觸java語法糖等;
(4)代碼生成器:調(diào)用com.sun.tools.javac.jvm.Gen類遍歷語法樹,生成最終字節(jié)碼;
如下圖2所示:
圖2 字節(jié)碼的生成流程圖
2.類加載過程Class 文件需要加載到虛擬機(jī)中之后才能運(yùn)行和使用,那么虛擬機(jī)是如何加載這些 Class 文件呢?
系統(tǒng)加載 Class 類型的文件主要三步:
加載->連接->初始化。連接過程又可分為三步:
驗(yàn)證->準(zhǔn)備->解析。如下圖3所示:
圖3 類加載流程圖
3.字節(jié)碼引擎字節(jié)碼引擎:
基于棧的字節(jié)碼解釋執(zhí)行引擎,其流程是抽象語法樹 -> 指令流(可選)-> 解析器 -> 解析執(zhí)行。
程序代碼到物理機(jī)的目標(biāo)代碼或者虛擬機(jī)執(zhí)行的指令之前,都需要經(jīng)過下圖中的各個(gè)步驟。下圖4中最下面的那條分支,就是傳統(tǒng)編譯原理中程序代碼到目標(biāo)機(jī)器代碼的生成過程;中間那條分支,則是解釋執(zhí)行的過程。
圖4 現(xiàn)代編譯流程
基于物理機(jī)、Java 虛擬機(jī)或者非 Java 的其它高級(jí)語言虛擬機(jī)的語言,大多都會(huì)遵循這種基于現(xiàn)代編譯原理的思路,在執(zhí)行前先對(duì)程序源代碼進(jìn)行詞法分析和語法分析處理,把源代碼轉(zhuǎn)化為抽象語法樹。對(duì)于一門具體語言的實(shí)現(xiàn)來說,詞法分析、語法分析以至后面的優(yōu)化器和目標(biāo)代碼生成器都可以選擇獨(dú)立于執(zhí)行引擎,形成一個(gè)完整意義的編譯器去實(shí)現(xiàn),這類代表是 C/C++。也可以為一個(gè)半獨(dú)立的編譯器,這類代表是 Java。又或者把這些步驟和執(zhí)行全部封裝在一個(gè)封閉的黑匣子中,如大多數(shù)的 JavaScript 執(zhí)行器。
Java 語言的編譯與解析是分開的,Javac 編譯器完成編譯,Java虛擬機(jī)完成解析。Javac 編譯器:程序代碼經(jīng)過
詞法分析、
語法分析、
抽象語法樹、
遍歷語法樹生成字節(jié)碼指令流的過程。 Java虛擬機(jī)的解析器負(fù)責(zé)解析字節(jié)碼,執(zhí)行器負(fù)責(zé)執(zhí)行。因此,Java 程序的編譯就是半獨(dú)立的實(shí)現(xiàn)。