01 JVM 從入門到實戰 | 什麼是 JVM

什麼是 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 虛擬機》這本書,此處貼個書中的圖過來:

圖來自於《深入理解 Java 虛擬機 第 2 版》

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

運行時數據區

這個區域描述的是 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 以前的版本基本是這樣的:

在這裏插入圖片描述

  • 新生代

    • Eden
    • s0
    • s1
  • 老年代

  • 永久代

在 JDK 1.8 以後的版本:

在這裏插入圖片描述

  • 新生代

  • 老年代

  • Meta Space

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

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

總結

  • JVM 負責軟件層面的機器碼翻譯,可以把我們寫的 .java 文件翻譯成機器可以識別的機器碼
  • JVM 負責內存管理
  • JVM 的運行時數據區包括方法區、堆、虛擬機棧、本地方法棧和程序計數器
  • JVM 中的方法區和堆區是所有線程共享的,其他區域都是線程獨享的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章