JVM 從入門到實戰--- 01 JVM 基本介紹

程式猿雜貨鋪發表於2019-03-12

什麼是 JVM

先來看下百度百科的解釋:

JVM 是 Java Virtual Machine(Java 虛擬機器)的縮寫,JVM 是一種用於計算裝置的規範,它是一個虛構出來的計算機,是通過在實際的計算機上模擬模擬各種計算機功能來實現的。

晦澀難懂有沒有,簡單理解就是說虛擬機器是物理機的軟體實現。

Java 的設計理念是 WORA(Write Once Run Anywhere,一次編寫到處執行)。編譯器將 Java 檔案編譯為 Java .class 檔案,然後將 .class 檔案輸入到 JVM 中,JVM 執行類檔案的載入和執行,最後轉變成機器可以識別的機器碼進行最終的操作。

為什麼要學習 JVM

每個 Java 開發人員都知道位元組碼經由 JRE(Java 執行時環境)執行。但他們或許不知道 JRE 其實是由 Java 虛擬機器(JVM)實現,JVM 分析位元組碼,解釋並執行它。作為開發人員,瞭解 JVM的 架構是非常重要的,因為它使我們能夠編寫出更高效的程式碼。

但是 JVM 在幫我們實現 Write Once Run Anywhere 的同時,有利有弊,因為在這個過程中涉及到了記憶體管理,尤其是多執行緒情況下的記憶體管理問題,所以我們更應該學習 JVM 的知識來幫助自己寫出更好的程式碼。

根據上邊對 JVM 的概念介紹我們知道,JVM 的主要作用在於以下兩方面,之後我們的介紹也會以此著手。

  • 軟體層面的機器碼翻譯

  • 記憶體管理

最近也在學習《深入理解 Java 虛擬機器》這本書,此處貼個書中的圖過來:

JVM 從入門到實戰--- 01 JVM 基本介紹

下邊就詳細介紹一下這張圖中的各個元件

執行時資料區

這個區域描述的是 Java 程式碼執行時的狀態,是我們非常關注的一個狀態-程式執行狀態,因為我們寫程式碼就是為了執行,不執行的狀態對我們是沒什麼吸引力的。說白了 Java 程式碼不外乎 資料 指令 控制 這三型別語句,所以我們將 JVM 執行時資料區可以劃分為如下兩大類:

  • 資料

    • 方法區

    • 堆(Heap)

  • 指令

    • 虛擬機器棧

    • 本地方法棧

    • 程式計數器

程式計數器

定義:指向當前執行緒正在執行的位元組碼指令的地址 也就是行號

注意:我們需要思考一個問題,我的當前執行緒本身已經在執行了,為什麼還要找個暫存器把他的執行行號記錄下來呢?

因為我們程式執行的最小單位是執行緒,而執行緒在 CPU 上執行的時候是搶佔式的,這樣的話就存線上程被掛起的情況,例如:有 A B 兩個執行緒,如果 A 執行緒執行過程被 B 執行緒搶佔了 CPU,則需要把掛起的 A 執行緒 當前執行到的行號儲存下來,等到 A 重新獲得 CPU 時間片執行權的時候去程式計數器獲得上一次執行的行號以便於繼續執行這個程式。

所以,每個執行緒都有自己的 程式計數器,而且是互不干擾的,屬於執行緒私有區域

  • 如果執行的是一個 Java 方法,計數器記錄的是正在執行的虛擬機器位元組碼指令的地址

  • 如果執行的是一個 Native 方法,計數器的值則為空(undefined)

虛擬機器棧

定義:儲存當前執行緒執行方法所需要的資料、指令和返回地址,生命週期與執行緒相同,同樣屬於執行緒私有區域

每個 Java 方法在執行的同時都會建立一個棧幀用於儲存區域性變數、運算元棧、方法出口等資訊,

如下所示,這個棧幀會儲存的資訊包括:

  • 區域性變數表

  • 運算元棧

  • 動態連結

  • 出口

  • ... ...

每一個方法從呼叫直至執行完成的過程,其實真正對應的是一個棧幀在虛擬機器棧中入棧到出棧的過程。

其中區域性變數表存放了編譯器可知的各種基本資料型別、引用物件等。需要注意的是因為區域性變數表空間長度只有 32 位,如果是 long 和 double 型別的話會佔用 2 個區域性變數表空間,其他資料型別只佔用 1 個。

注意:區域性變數表所需的記憶體空間在編譯期間就會車隊分配完成,因為在進入一個方法時,這個方法需要在棧幀中分配多大的區域性空間是完全確定的,方法執行期間區域性變數大小是不會改變的。

本地方法棧

和虛擬機器棧類似,只不過他儲存的是當前執行緒呼叫的本地方法所需要的資料、指令和返回地址等,本地方法時標識有 Native 關鍵字的方法,此處就不展開描述了,參考上述虛擬機器棧的介紹。

另外,根據《深入理解 Java 虛擬機器》這本書的介紹,有些虛擬機器(如 Sun HotSpot 虛擬機器)直接就把本地方法棧和虛擬機器棧合二為一了。

方法區

這塊區域屬於執行緒共享群與,主要儲存的資訊包括已被虛擬機器你載入的類資訊(類的元資訊)、常量、靜態變數、JIT(編譯器編譯後的程式碼)等資料。

方法區有一塊區域我們稱之為 執行時常量池,存放編譯期生成的各種字面量和符號引用,執行時常量池有一個重要特徵是具備動態性,也就是說在執行期間依然可以將新的常量放入池中,我們開發常用的有 String 類的 intern() 方法

堆(Heap)

屬於執行緒共享區域,在虛擬機器啟動時建立,是虛擬機器管理的記憶體中最大的一塊。它的唯一作用就是存放物件例項。

根據虛擬機器規範的描述是:所有的物件例項及陣列都要在堆上分配。當然隨著現在技術的發展優化這個也變得沒有那麼絕對,後續會進行分享。

這塊區域也是垃圾收集器管理的主要區域,現如今流行的垃圾回收器基本都採用的是分代收集演算法,所以也就衍生了一些分代方式,

比如對於記憶體模型的劃分,在 JDK1.8 以前的版本基本是這樣的:

JVM 從入門到實戰--- 01 JVM 基本介紹

  • 新生代

    • Eden

    • s0

    • s1

  • 老年代

  • 永久代

在 JDK 1.8 以後的版本:

JVM 從入門到實戰--- 01 JVM 基本介紹

  • 新生代

  • 老年代

  • Meta Space

此處小提一下,之所以在 JDK 1.8 以後 有了 Meta Space,其設計的目的在於規避永久代溢位的問題,因為 Meta Space 是可以自動擴容的,就跟 Java 中的集合一樣。

以上種種的劃分方式,都是為了更好地回收記憶體或者分配記憶體,從下一篇開始就開始學習記憶體分配及垃圾回收相關演算法啦!

總結

  • JVM 負責軟體層面的機器碼翻譯,可以把我們寫的 .java 檔案翻譯成機器可以識別的機器碼

  • JVM 負責記憶體管理

  • JVM 的執行時資料區包括方法區、堆、虛擬機器棧、本地方法棧和程式計數器

  • JVM 中的方法區和堆區是所有執行緒共享的,其他區域都是執行緒獨享的

JVM 從入門到實戰--- 01 JVM 基本介紹

相關文章