天天看點

教妹學Java(七):究竟什麼是JVM?

“二哥,最近疫情鬧得人心惶惶,無心學習了,怎麼辦?”

“三妹啊,你要知道,曆史上經曆過無數次的動蕩與不安,但最後,都挺過去了。有人破壞,有人修複。安安心心學習,疫情過後肯定能夠派上大用場,到時候經濟複蘇,人才亟需,懂嗎?”

“二哥,說話就是不一樣,一句話就安撫了我不安的心情。”

“那還不開始今天的主題?”

“二哥,上一篇文章中你給我解釋了什麼是 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)。

教妹學Java(七):究竟什麼是JVM?

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)運作時資料區

運作時資料區又包含以下内容。

教妹學Java(七):究竟什麼是JVM?

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 已經考慮漲價了,畢竟已經賣出一百多份了。