1. 程式人生 > >小師妹學JVM之:JVM的架構和執行過程

小師妹學JVM之:JVM的架構和執行過程

[toc] # 簡介 JVM也叫Java Virtual Machine,它是java程式執行的基礎,負責將java bytecode轉換成為適合在各個不同作業系統中執行的機器程式碼並執行。今天我們和小師妹一起走進java的核心JVM,領略java在設計上的哲學。 # JVM是一種標準 小師妹:F師兄,經常聽到有人說hotspot VM,這個跟JVM是什麼關係? 其實吧,JVM只是一種標準,就像是一種協議,只要是實現和滿足這種協議的都可以稱為JVM。當然,java現在是Oracle公司的,所以這些所謂的JVM標準也是由Oracle來頒佈的,如果你去檢視Oracle的文件,就會發現有一個專門的Java SE Specifications欄目,這個欄目中列出了JVM的實現標準,最新的標準就是The Java Virtual Machine Specification, Java SE 14 Edition。 更多精彩內容且看: * [區塊鏈從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特幣等持續更新](http://www.flydean.com/blockchain/) * [Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新](http://www.flydean.com/learn-spring-boot/) * [Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新](http://www.flydean.com/spring5/) * [java程式設計師從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程](http://www.flydean.com/java-roadmap-2020/) > 更多內容請訪問[www.flydean.com](www.flydean.com) 既然JVM是一個標準,就可能有很多種實現。各大公司在滿足JVM標準的基礎上,開發了很多個不同的版本。 下面是我在維基百科中擷取到的目前各個JVM的比較: ![](https://img-blog.csdnimg.cn/20200524203518218.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70) 小師妹:F師兄,大家齊心協力做一個JVM不是更好嗎?為什麼分來分去的,還要重複造輪子? 有聽過Oracle和Google之間的API十年訴訟案嗎?API都不能順便用,更何況是JVM。各大廠商為了各自的利益,最終搞出了這麼多個JVM的版本。 在這些JVM中,最常用的就是HotSpot JVM了,畢竟它是Oracle的親兒子,或者可以說HotSpot JVM就是JVM的標準。 接下來就是Eclipse OpenJ9,這個是由IBM主導的JVM,一般只能跟IBM的產品一起使用的,因為有許可證限制。 # java程式的執行順序 為了說明JVM的作用,我們先來回顧一下java程式的執行順序。 ![](https://img-blog.csdnimg.cn/20200524212920415.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70) 1. 編寫java程式碼檔案比如Example.java 2. 使用java編譯器javac將原始檔編譯成為Example.class檔案 3. JVM載入生成的位元組碼檔案,將其轉換成為機器可以識別的native machine code執行 # JVM的架構 小師妹:F師兄,Java語言那麼多特性,最後都要在JVM中執行,JVM的架構是不是特別複雜?好怕我聽不懂。 其實吧,JVM可以分為三大部分,五大空間和三大引擎,要講起來也不是特別複雜,先看下面的總體的JVM架構圖。 ![](https://img-blog.csdnimg.cn/20200524221637660.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70) 從上面的圖中,我們可以看到JVM中有三大部分,分別是類載入系統,執行時資料區域和Execution Engine。 ## 類載入系統 類載入系統分為三個階段,分別是載入,連結和初始化。 載入大家都很清楚了,java中有個專門的ClassLoader來負責這個事情。除了載入Class之外,ClassLoader還可以用來載入resources。 在JDK9之前,系統預設有三個類載入器,分別是: 1. Bootstrap ClassLoader 這個類載入器主要是載入 /jre/lib下面的rt.jar,並且這個類載入器是用C/C++來編寫的,並且它是後面Extension ClassLoader的父ClassLoader。 這個類應該在java程式碼中找不到的(correct me if I am wrong!)。 2. Extension ClassLoader 這個類載入器主要載入JDK的擴充套件類 /jre/lib/ext,它的實現類是 sun.misc.Launcher$ExtClassLoader : ~~~java static class ExtClassLoader extends URLClassLoader { private static volatile Launcher.ExtClassLoader instance; public static Launcher.ExtClassLoader getExtClassLoader() throws IOException { if (instance == null) { Class var0 = Launcher.ExtClassLoader.class; synchronized(Launcher.ExtClassLoader.class) { if (instance == null) { instance = createExtClassLoader(); } } } return instance; } ~~~ 我們看下它的實現,實際上它建立了一個單例模式,使用的是雙重檢查加鎖,小師妹可以考慮一下怎麼使用延遲初始化佔位類的方式來重新這個類。 3. System ClassLoader 這個載入器是載入定義在ClassLoader中的類。它的實現類是sun.misc.Launcher$AppClassLoader,這個類的實現很長,這裡就不完整列出來了: ~~~java static class AppClassLoader extends URLClassLoader ~~~ 在JDK9之後,因為引入了JPMS模組的概念,所以類載入器變得不一樣了,在JDK9之後還是有三個內建的類載入器,分別是BootClassLoader,PlatformClassLoader和AppClassLoader: ~~~java private static class BootClassLoader extends BuiltinClassLoader { BootClassLoader(URLClassPath bcp) { super(null, null, bcp); } @Override protected Class loadClassOrNull(String cn, boolean resolve) { return JLA.findBootstrapClassOrNull(this, cn); } }; ~~~ ~~~java private static class PlatformClassLoader extends BuiltinClassLoader ~~~ ~~~java private static class AppClassLoader extends BuiltinClassLoader ~~~ Linking階段主要做了三件事情: 1. Verification - 主要驗證位元組碼檔案的結構的正確性,如果不正確則會報LinkageError。 2. Preparation - 負責建立static fields,並且初始化他們的值。 3. Resolution - 把型別的常量池中引用的類,介面,欄位和方法替換為直接引用的過程。 Initialization階段主要是呼叫class的父類和自身的初始化方法,來設定變數的初始值。 ## 執行時資料區域 類載入好了,也初始化了,接下來就可以準備運行了。 執行的時候要為資料分配執行空間,這就是執行時資料區域的作用。 執行時資料區域又可以分為5個部分: 1. Method Area 方法區是非Heap的記憶體空間,主要用來存放class結構,static fields, method, method’s data 和 static fields等。方法區是在JVM啟動的時候建立的,並且在所有的執行緒中共享。 > Run-Time Constant Pool執行時常量池是放在方法區中的,他是class檔案中constant_pool的執行時表現。 > 注意在JDK8之前,HotSpot JVM中對方法區的實現叫做持久代Perm Gen。不過在JDK8之後,Perm Gen已經被取消了,現在叫做Metaspace。Metaspace並不在java虛擬機器中,它使用的是本地記憶體。Metaspace可以通過-XX:MaxMetaspaceSize來控制。 2. Heap Area Heap Area主要儲存類物件和陣列。垃圾回收器(GC)主要就是用來回收Heap Area中的物件的。 3. Stack Area 因為是棧的結構,所以這個區域總是LIFO(Last in first out)。我們考慮一個方法的執行,當方法執行的時候,就會在Stack Area中建立一個block,這個block中持有對本地物件和其他物件的引用。一旦方法執行完畢,則這個block就會出棧,供其他方法訪問。 4. PC Registers PC Registers主要用來對程式的執行狀態進行跟蹤,比如儲存當前的執行地址,和下一步的地址等。 5. Native Methods 最後一個就是本地方法區了,因為JVM的底層很多都是由C/C++來實現的,這些方法的實現就構成了本地方法區。 ## 執行引擎 執行引擎主要負責將java的位元組碼翻譯成機器碼然後執行。 先看一個java位元組碼的內在結構,大家可以隨便找一個編譯好的類,使用javap來進行解析: ~~~java javap -v BufferUsage ~~~ ![](https://img-blog.csdnimg.cn/20200525093536806.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70) 這裡不過多介紹輸出結果的含義,我們會在後面的文章中進行詳解。 這我們可以看到方法中都有一個Code片段,這些Code被稱為OpCode,是JVM可以理解的操作命令。 執行引擎中裡面又有三個部分: 1. Interpreter 翻譯器用來讀取上面介紹的OpCode,並將其翻譯成為機器語言。因為翻譯器需要一個命令一個命令的翻譯位元組碼,所以速度會比較慢。這就是很久很久以前Java被詬病的地方。 2. JIT (Just-In-Time) compiler 為了解決Interpreter翻譯慢的問題,JDK引入了JIT,對於那些經常使用的程式碼,JIT會將這些位元組碼翻譯成為機器程式碼,並直接複用這些機器程式碼,從而提高了執行效率。 3. Garbage Collector GC用來回收Heap Area,他是一個Daemon thread。 # 總結 本文介紹了JVM的總體架構資訊。各個部分的細節資訊會在後面的系列文章中陸續講解。歡迎大家關注小師妹系列。 > 本文作者:flydean程式那些事 > > 本文連結:[http://www.flydean.com/jvm-all-in-one/](http://www.flydean.com/jvm-all-in-one/) > > 本文來源:flydean的部落格 > > 歡迎關注我的公眾號:程式那些事,更多精彩等著您!