小師妹學JVM之:JVM的架構和執行過程
簡介
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,比特幣等持續更新
- Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新
- Spring 5.X系列教程:滿足你對Spring5的一切想像-持續更新
- java程序員從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程
更多內容請訪問www.flydean.com
既然JVM是一個標準,就可能有很多種實現。各大公司在滿足JVM標準的基礎上,開發了很多個不同的版本。
下面是我在維基百科中截取到的目前各個JVM的比較:
小師妹:F師兄,大家齊心協力做一個JVM不是更好嗎?為什麼分來分去的,還要重複造輪子?
有聽過Oracle和Google之間的API十年訴訟案嗎?API都不能順便用,更何況是JVM。各大廠商為了各自的利益,最終搞出了這麼多個JVM的版本。
在這些JVM中,最常用的就是HotSpot JVM了,畢竟它是Oracle的親兒子,或者可以說HotSpot JVM就是JVM的標準。
接下來就是Eclipse OpenJ9,這個是由IBM主導的JVM,一般只能跟IBM的產品一起使用的,因為有許可證限制。
java程序的執行順序
為了說明JVM的作用,我們先來回顧一下java程序的執行順序。
- 編寫java代碼文件比如Example.java
- 使用java編譯器javac將源文件編譯成為Example.class文件
- JVM加載生成的位元組碼文件,將其轉換成為機器可以識別的native machine code執行
JVM的架構
小師妹:F師兄,Java語言那麼多特性,最後都要在JVM中運行,JVM的架構是不是特別複雜?好怕我聽不懂。
其實吧,JVM可以分為三大部分,五大空間和三大引擎,要講起來也不是特別複雜,先看下面的總體的JVM架構圖。
從上面的圖中,我們可以看到JVM中有三大部分,分別是類加載系統,運行時數據區域和Execution Engine。
類加載系統
類加載系統分為三個階段,分別是加載,鏈接和初始化。
加載大家都很清楚了,java中有個專門的ClassLoader來負責這個事情。除了加載Class之外,ClassLoader還可以用來加載resources。
在JDK9之前,系統默認有三個類加載器,分別是:
- Bootstrap ClassLoader
這個類加載器主要是加載 /jre/lib下面的rt.jar,並且這個類加載器是用C/C++來編寫的,並且它是後面Extension ClassLoader的父ClassLoader。
這個類應該在java代碼中找不到的(correct me if I am wrong!)。
- Extension ClassLoader
這個類加載器主要加載JDK的擴展類 /jre/lib/ext,它的實現類是 sun.misc.Launcher$ExtClassLoader :
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;
}
我們看下它的實現,實際上它創建了一個單例模式,使用的是雙重檢查加鎖,小師妹可以考慮一下怎麼使用延遲初始化佔位類的方式來重新這個類。
- System ClassLoader
這個加載器是加載定義在ClassLoader中的類。它的實現類是sun.misc.Launcher$AppClassLoader,這個類的實現很長,這裡就不完整列出來了:
static class AppClassLoader extends URLClassLoader
在JDK9之後,因為引入了JPMS模塊的概念,所以類加載器變得不一樣了,在JDK9之後還是有三個內置的類加載器,分別是BootClassLoader,PlatformClassLoader和AppClassLoader:
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);
}
};
private static class PlatformClassLoader extends BuiltinClassLoader
private static class AppClassLoader extends BuiltinClassLoader
Linking階段主要做了三件事情:
- Verification – 主要驗證位元組碼文件的結構的正確性,如果不正確則會報LinkageError。
- Preparation – 負責創建static fields,並且初始化他們的值。
- Resolution – 把類型的常量池中引用的類,接口,字段和方法替換為直接引用的過程。
Initialization階段主要是調用class的父類和自身的初始化方法,來設置變量的初始值。
運行時數據區域
類加載好了,也初始化了,接下來就可以準備運行了。
運行的時候要為數據分配運行空間,這就是運行時數據區域的作用。
運行時數據區域又可以分為5個部分:
- 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來控制。
- Heap Area
Heap Area主要存儲類對象和數組。垃圾回收器(GC)主要就是用來回收Heap Area中的對象的。
- Stack Area
因為是棧的結構,所以這個區域總是LIFO(Last in first out)。我們考慮一個方法的執行,當方法執行的時候,就會在Stack Area中創建一個block,這個block中持有對本地對象和其他對象的引用。一旦方法執行完畢,則這個block就會出棧,供其他方法訪問。
- PC Registers
PC Registers主要用來對程序的執行狀態進行跟蹤,比如保存當前的執行地址,和下一步的地址等。
- Native Methods
最後一個就是本地方法區了,因為JVM的底層很多都是由C/C++來實現的,這些方法的實現就構成了本地方法區。
執行引擎
執行引擎主要負責將java的位元組碼翻譯成機器碼然後執行。
先看一個java位元組碼的內在結構,大家可以隨便找一個編譯好的類,使用javap來進行解析:
javap -v BufferUsage
這裡不過多介紹輸出結果的含義,我們會在後面的文章中進行詳解。
這我們可以看到方法中都有一個Code片段,這些Code被稱為OpCode,是JVM可以理解的操作命令。
執行引擎中裏面又有三個部分:
- Interpreter
翻譯器用來讀取上面介紹的OpCode,並將其翻譯成為機器語言。因為翻譯器需要一個命令一個命令的翻譯位元組碼,所以速度會比較慢。這就是很久很久以前Java被詬病的地方。
- JIT (Just-In-Time) compiler
為了解決Interpreter翻譯慢的問題,JDK引入了JIT,對於那些經常使用的代碼,JIT會將這些位元組碼翻譯成為機器代碼,並直接復用這些機器代碼,從而提高了執行效率。
- Garbage Collector
GC用來回收Heap Area,他是一個Daemon thread。
總結
本文介紹了JVM的總體架構信息。各個部分的細節信息會在後面的系列文章中陸續講解。歡迎大家關注小師妹系列。
本文作者:flydean程序那些事
本文鏈接://www.flydean.com/jvm-all-in-one/
本文來源:flydean的博客
歡迎關注我的公眾號:程序那些事,更多精彩等着您!