“二哥,最近疫情鬧得人心惶惶,無心學習了,怎麼辦?”
“三妹啊,你要知道,曆史上經曆過無數次的動蕩與不安,但最後,都挺過去了。有人破壞,有人修複。安安心心學習,疫情過後肯定能夠派上大用場,到時候經濟複蘇,人才亟需,懂嗎?”
“二哥,說話就是不一樣,一句話就安撫了我不安的心情。”
“那還不開始今天的主題?”
“二哥,上一篇文章中你給我解釋了什麼是 JDK,JRE 和 JVM,但我想知道 JVM 究竟是什麼,它能幹什麼事。”
“三妹啊,搬個凳子坐我旁邊,聽二哥來給你慢慢說啊。”
01、什麼是 JVM
再來回顧一下。JVM(Java Virtual Machine)俗稱 Java 虛拟機。之是以稱為虛拟機,是因為它實際上并不存在。它提供了一種運作環境,可供 Java 位元組碼在上面運作。
02、JVM 能做什麼
JVM 提供了以下操作:
加載位元組碼
驗證位元組碼
執行位元組碼
提供運作時環境
JVM 定義了以下内容:
存儲區
類檔案格式
寄存器組
垃圾回收堆
緻命錯誤報告等
03、JVM 的内部結構
我們來嘗試了解一下 JVM 的内部結構,它包含了類加載器(Class Loader)、運作時資料區(Runtime Data Areas)和執行引擎(Excution Engine)。

1)類加載器
類加載器是 JVM 的一個子系統,用于加載類檔案。每當我們運作一個 Java 程式,它都會由類加載器首先加載。Java 中有三個内置的類加載器:
啟動類加載器(Bootstrap Class-Loader),加載 jre/lib 包下面的 jar 檔案,比如說常見的 rt.jar(包含了 Java 标準庫下的所有類檔案,比如說 java.lang 包下的類,java.net 包下的類,java.util 包下的類,java.io 包下的類,java.sql 包下的類)。
擴充類加載器(Extension or Ext Class-Loader),加載 jre/lib/ext 包下面的 jar 檔案。
應用類加載器(Application or App Clas-Loader),根據程式的類路徑(classpath)來加載 Java 類。
一般來說,Java 程式員并不需要直接同類加載器進行互動。JVM 預設的行為就已經足夠滿足大多數情況的需求了。不過,如果遇到了需要和類加載器進行互動的情況,而對類加載器的機制又不是很了解的話,就不得不花大量的時間去調試
ClassNotFoundException 和 NoClassDefFoundError 等異常。
對于任意一個類,都需要由它的類加載器和這個類本身一同确定其在 JVM 中的唯一性。也就是說,如果兩個類的加載器不同,即使兩個類來源于同一個位元組碼檔案,那這兩個類就必定不相等(比如兩個類的 Class 對象不 equals)。
三妹啊,是不是有點暈,來來來,通過一段簡單的代碼了解下。
public class Test {
public static void main(String[] args) {
ClassLoader loader = Test.class.getClassLoader();
while (loader != null) {
System.out.println(loader.toString());
loader = loader.getParent();
}
}
}
每個 Java 類都維護着一個指向定義它的類加載器的引用,通過 類名.class.getClassLoader() 可以擷取到此引用;然後通過 loader.getParent() 可以擷取類加載器的上層類加載器。
上面這段代碼的輸出結果如下:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@4617c264
第一行輸出為 Test 的類加載器,即應用類加載器,它是 sun.misc.Launcher$AppClassLoader 類的執行個體;第二行輸出為擴充類加載器,是 sun.misc.Launcher$ExtClassLoader 類的執行個體。那啟動類加載器呢?
按理說,擴充類加載器的上層類加載器是啟動類加載器,但在我這個版本的 JDK 中, 擴充類加載器的 getParent() 傳回 null。是以沒有輸出。
2)運作時資料區
運作時資料區又包含以下内容。
PC寄存器(PC Register),也叫程式計數器(Program Counter Register),是一塊較小的記憶體空間,它的作用可以看做是目前線程所執行的位元組碼的信号訓示器。
JVM 棧(Java Virtual Machine Stack),與 PC 寄存器一樣,JVM 棧也是線程私有的。每一個 JVM 線程都有自己的 JVM 棧,這個棧與線程同時建立,它的生命周期與線程相同。
本地方法棧(Native Method Stack),JVM 可能會使用到傳統的棧來支援 Native 方法(使用 Java 語言以外的其它語言[C語言]編寫的方法)的執行,這個棧就是本地方法棧。
堆(Heap),在 JVM 中,堆是可供各條線程共享的運作時記憶體區域,也是供所有類執行個體和資料對象配置設定記憶體的區域。
方法區(Method area),在 JVM 中,被加載類型的資訊都儲存在方法區中。包括類型資訊(Type Information)和方法清單(Method Tables)。方法區是所有線程共享的,是以通路方法區資訊的方法必須是線程安全的。
運作時常量池(Runtime Constant Pool),運作時常量池是每一個類或接口的常量池在運作時的表現形式,它包括了編譯器可知的數值字面量,以及運作期解析後才能獲得的方法或字段的引用。簡而言之,當一個方法或者變量被引用時,JVM 通過運作時常量區來查找方法或者變量在記憶體裡的實際位址。
3)執行引擎
執行引擎包含了:
解釋器:讀取位元組碼流,然後執行指令。因為它一條一條地解釋和執行指令,是以它可以很快地解釋位元組碼,但是執行起來會比較慢。
即時(Just-In-Time,JIT)編譯器:即時編譯器用來彌補解釋器的缺點,提高性能。執行引擎首先按照解釋執行的方式來執行,然後在合适的時候,即時編譯器把整段位元組碼編譯成本地代碼。然後,執行引擎就沒有必要再去解釋執行方法了,它可以直接通過本地代碼去執行。執行本地代碼比一條一條進行解釋執行的速度快很多。編譯後的代碼可以執行的很快,因為本地代碼是儲存在緩存裡的。
04、鳴謝
本篇文章為《教妹學Java》專欄的第七篇文章,是不是有趣得很?我相信你能感受的到,這可是全網獨一份,我看到已經有人在模仿了。現在定價隻需 9.9 元,9.9 元你連一杯奶茶都買不到,但卻能買下二哥精心制作的專欄,據說 CSDN 已經考慮漲價了,畢竟已經賣出一百多份了。