1. 程式人生 > >JVM筆記 -- JVM的發展以及基於棧的指令集架構

JVM筆記 -- JVM的發展以及基於棧的指令集架構

- 1. 2011年,`JDK7`釋出,1.7u4中,開始啟用新的垃圾回收器`G1`(但是不是預設)。 - 2. 2017年,釋出`JDK9`,`G1`成為預設`GC`,代替`CMS`。(一般公司使用`jdk8`的時候,會通過引數,指定`GC`為`G1`) - 3. 2018年,釋出`JDK11`,帶來了革命性`ZGC`,效能比較強。 ## 虛擬機器介紹 虛擬機器,就是虛擬的計算機,可以執行一系列虛擬計算機指令,大體上可以分為系統虛擬機器和程式虛擬機器。它們執行時,都會受到虛擬機器提供的資源的限制。 - 系統虛擬機器:模擬模擬系統的,比如`Visual Box`,`VMware`。 - 程式虛擬機器:為執行單個計算機程式設計的,比如`Java`虛擬機器。 ## JAVA虛擬機器 `Java`虛擬機器是一臺執行位元組碼的虛擬機器計算機,但是位元組碼不一定是由`Java`語言編譯而成。但是隻要使用這一套虛擬機器規則的語言,就可以享受到跨平臺,垃圾收集以及可靠的即時編譯器。`JVM`和硬體之間沒有直接的互動。 - 一次編譯,到處執行。 - 自動記憶體管理 - 自動垃圾回收 下面是ava平臺文件中Java概念圖的描述,可以看出`javac`命令在`JDK`中,也就是將`.java`檔案編譯成為`.class`檔案,這個就是前端編譯器,將原始檔編譯成為位元組碼。這個編譯器不在`JRE`中,也說明了`JRE`不包括編譯環境。 JRE和JDK都包括了JVM虛擬機器。JRE是執行時環境,而JDK包含了開發環境。 JDK7 中java家族的結構組成 : https://docs.oracle.com/javase/7/docs/ ![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/20210124190030.png) JDK7 中java家族的結構組成 : https://docs.oracle.com/javase/8/docs/ ![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/20210124190230.png) ## JVM結構 ![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/20210124193442.png) 上面的圖主要包括三部分:類載入器,執行時資料區,執行引擎。 類載入器,主要是將Class檔案(已經經過前端編譯器編譯後的位元組碼檔案),載入到執行時資料區,生成Class物件,這個過程會設計載入,連結,初始化等過程。 執行時區域主要分為: - 執行緒私有(每個執行緒有一份): - 程式計數器:`Program Count Register`,執行緒私有,沒有垃圾回收 - 虛擬機器棧:`VM Stack`,執行緒私有,沒有垃圾回收 - 本地方法棧:`Native Method Stack`,執行緒私有,沒有垃圾回收 - 執行緒共享: - 方法區:`Method Area`,以`HotSpot`為例,`JDK1.8`後元空間取代方法區,有垃圾回收。 - 堆:`Heap`,垃圾回收最重要的地方。 執行引擎主要包括直譯器和即時編譯器(熱點程式碼提前編譯好,這是後端編譯器),垃圾回收器。位元組碼檔案不能直接被機器識別,所以需要執行引擎來做轉換。 ## Java程式碼執行流程 Java程式碼變成位元組碼檔案的過程中,其實包含了詞法分析,語法分析,語法樹,語義分析等一系列操作。 ![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/20210124203318.png) 在執行引擎中,有JIT編譯器,也就是第二次編譯的過程會發生在這裡,會將熱點程式碼編譯成為機器指令,是按照方法的維度,快取起來(放在方法區),也稱之為`CodeCache`。 ## JVM架構模型 Java編譯器主要是基於棧的指令集架構,個人覺得主要原因是可移植性決定的,JVM需要跨平臺。指令集架構主要有兩種: - 基於棧的指令集架構:一個方法相當於一個入棧的操作,執行完相當於出棧操作。 - 基於暫存器的指令集架構 ### 基於棧的指令集架構的特點 主要特點: - 設計實現簡單,適用於資源受限的系統,比如機頂盒,小玩具上。 - 避開暫存器分配難題:使用零地址指令方式分配。 - 指令流中大部分都是零地址指令,執行過程依賴操作棧,指令集更小(零地址),編譯器容易實現。 - 不需要硬體支援,可移植性強,容易實現跨平臺。 ### 基於暫存器架構的特點 - 典型應用是x86的二進位制指令集 - 依賴於硬體,可移植性差 - 效能好,執行效率高 - 更少指令執行一項操作 - 大部分情況下,暫存器的架構,一,二,三地址指令為主,而基於棧的指令集卻是以零地址指令為主。 **說明:什麼叫零地址指令,一地址指令,二地址指令?** 零地址指令只有操作碼,沒有運算元。這種指令有兩種情況:一是無需運算元,另一種是運算元為預設的(隱含的),預設為運算元在暫存器中,指令可直接訪問暫存器。 - 三地址指令:一般地址域中A1、A2分別確定第一、第二運算元地址,A3確定結果地址。下一條指令的地址通常由程式計數器按順序給出。 - 二地址指令:地址域中A1確定第一運算元地址,A2同時確定第二運算元地址和結果地址。 - 單地址指令:地址域中A 確定第一運算元地址。固定使用某個暫存器存放第二運算元和操作結果。因而在指令中隱含了它們的地址。 - 零地址指令:在堆疊型計算機中,運算元一般存放在下推堆疊頂的兩個單元中,結果又放入棧頂,地址均被隱含,因而大多數指令只有操作碼而沒有地址域。 棧資料結構,一般只有入棧和出棧,所以操作的地方只有棧頂元素,所以位置是確定的,不需要地址。 **例子** 執行2+3的操作,如果是基於棧的計算流程: ```txt iconst_2 // 常量2入棧 istore_1 iconst_3 // 常量3入棧 istore_2 iload_1 iload_2 iadd // 常量2,3出棧,執行相加 istore_0 // 結果5入棧 ``` 基於暫存器的計算流程: ```txt mov eax,2 //將eax暫存器的值設定為2 add eax,3 // 將eax暫存器的值加3 ``` 從上面的例子可以看出來,基於棧的暫存器的指令更小,但是基於暫存器的指令更少。 我們可以通過一個簡單程式看一下: ```java public class StackStructTest { public static void main(String[] args) { int i = 2 + 3; } } ``` 編譯後,切換到`class`目錄下,使用命令反編譯: ```shell java -v StackStructTest.class ``` ![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/20210209025253.png) 看到位元組碼的模組,可以看到前面有`iconst_5`,其實`5`就是`2+3`的結果,也就是編譯期間就會直接把`2+3`變成`5`,不會在執行的時候才去計算,這個是因為`2`和`3`都是常量。 這個現象稱之為**編譯期的常量摺疊**。 ![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/20210209025413.png) 但是如果我們把程式碼成下面這種情況呢? ```java int i = 2; int j = 3; int k = i + j; ``` 反編譯出來的指令: ![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/20210209031138.png) `const`意思是`constant`(常量),`store`是`storeage`暫存器。 ```txt stack=2, locals=4, args_size=1 0: iconst_2 // 2是個常量 1: istore_1 // 2載入到1號運算元棧 2: iconst_3 // 3是一個產量 3: istore_2 // 3載入到2號運算元棧 4: iload_1 // 將1號運算元棧取出,載入進來 5: iload_2 // 將2號運算元棧取出,載入進來 6: iadd // 兩者相加 7: istore_3 // 結果儲存到索引為3號運算元棧中 8: return ``` 也就是棧架構的`JVM`,需要 8 條指令才能完成上面的變數相加計算。 **棧架構總結** 由於跨平臺特性,Java指令基於棧來設計,因為不同的CPU架構不同,優點是跨平臺,指令集小,編譯器容易實現。缺點是效能下降,實現同樣功能需要更多指令。 **【作者簡介】**: 秦懷,公眾號【**秦懷雜貨店**】作者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。個人寫作方向:Java原始碼解析,JDBC,Mybatis,Spring,redis,分散式,劍指Offer,LeetCode等,認真寫好每一篇文章,不喜歡標題黨,不喜歡花裡胡哨,大多寫系列文章,不能保證我寫的都完全正確,但是我保證所寫的均經過實踐或者查詢資料。遺漏或者錯誤之處,還望指正。 [2020年我寫了什麼?](http://aphysia.cn/archives/2020) [開源程式設計筆記](https://damaer.github.io/Cod