1. 程式人生 > >深入淺出 JVM 系列(一)什麼是 JVM?它處於什麼位置?

深入淺出 JVM 系列(一)什麼是 JVM?它處於什麼位置?

閱讀本文大概需要 5.6 分鐘

前言

在 Java 開發中,我們經常會提到 JVM。我們知道 JVM 是 Java 虛擬機器,但是它的執行原理是什麼?它的記憶體結構是什麼?如何進行優化?如何去定位問題?面試中遇到 JVM 問題如何回答?

接下來我會開啟 JVM 的章節,為大家一一解答上面的問題。現在就開啟我們的 JVM 學習之路吧!

什麼是 JVM?它處於什麼位置?

面試官:什麼是 JVM?

小李:JVM(Java Virtual Machine)是 Java 虛擬機器,用於執行 Java 編譯後的二進位制位元組碼,最後生成機器指令。(心裡一想,簡簡單單)

面試官:那為什麼 Java 研發體系需要 JVM?你對 JVM 的執行原理了解多少?我們寫的 Java 程式碼到底是如何執行起來的?

小李:嗯。。。就是。。。嗯。。。是那個。。。嗯。。。

面試官:面試就到這裡了,先回去等通知吧。

小李:好的!(哭著回答)


這裡面試官對小李進行三連問:

  1. 為什麼 Java 研發體系需要 JVM?
  2. 你對 JVM 的執行原理了解多少?
  3. 我們寫的 Java 程式碼到底是如何執行起來的?

這套組合拳看似很厲害,其實就是軍體拳。

如果想完美的練這套軍體拳,不,是完美的回答這三個問題,就需要首先要了解 JVM 是什麼?它和 Java 是什麼關係?又和 JDK 有什麼淵源?那要弄清楚這些問題,就需要從三個維度去思考:

  • JVM 和作業系統的關係;
  • JVM 、JRE、JDK 的關係;
  • Java 虛擬機器規範和 Java 語言規範的關係。

弄清楚這這幾者的關係,我們再通過一個簡單程式碼示例來看一個 Java 程式到底是如何執行的。

JVM 和 作業系統的關係

我們知道煉製一把牛逼的大寶劍,不僅需要上等的技術,還需要一鼎經百鍊的劍爐。而工程師就相當於鑄劍的劍師,JVM 便是劍爐。

JVM 就是我們耳熟能詳的 Java 虛擬機器。它能識別 .class 字尾檔案,並且能夠解析它的指令,最終呼叫作業系統上的函式,完成我們想要的操作。

Java 程式和 C++ 程式有什麼不同呢?這裡用兩張圖進行說明。

對比兩張圖可以看到 C++ 開發的程式可以翻譯成作業系統能識別的 .exe 檔案。而 Java 程式需要通過 javac 編譯成 .class 檔案之後,然後由 JVM 負責呼叫系統函式執行程式,作業系統並不認識 .class 檔案。

那讀者就勸小李了,轉 C++ 開發吧,這 Java 還搞了一個處於程式和作業系統的虛擬機器,不像 C++ 編譯後直接在作業系統上執行,肯定不是啥好玩意。

我就知道你們壞的很,知道 JVM 的過人之處,還不告訴小李。那我給小李講講 JVM 的過人之處:

Java 是一門抽象度特別高的語言,提供了自動記憶體管理等一系列的特性。這些特性在作業系統上基本上是無望了,所以就需要 JVM 進行一番轉換。

經過上面的介紹,我們可以做如下的類比:

  • JVM:等同於作業系統;
  • Java 位元組碼:等同於組合語言。

Java 位元組碼還是比較容易讀懂,從側面上也證明了 Java 語言的抽象程度高。我們可以認為 JVM 是一個翻譯器,會持續不斷的翻譯執行 Java 位元組碼,然後呼叫真正的作業系統函式,這些作業系統函式是與平臺息息相關的。

可以把 JVM 想象一個有道詞典,.class 檔案是英文,而輸出的結果是中文。有道詞典有 windows版本,也有 Linux 版本,內部具體的實現肯定不同,但最終都會得到相同的結果,這樣就好理解一些了)

當有個 JVM 這個抽象層,就可以實現跨平臺了。JVM 只需要正確執行 .class 檔案,就可以執行在 Linux、Windos、MacOS 等平臺了。

Java 跨平臺的意義在於一次編譯,處處執行,這裡 JVM 功不可沒。比如在 Maven 倉庫下載的 jar 包就可以到處執行,不需要在每個平臺上再編譯一次。

我們來概括 JVM 與作業系統之間的關係:

JVM 上承開發語言,下接作業系統,它的中間介面就是位元組碼。

JVM、JRE、JDK 的關係

通過上面的學習,我們瞭解到 JVM 是 Java 程式能夠執行的核心。但是我們要知道,JVM 自己什麼也幹不了,你需要給它提供原料(.class 檔案)。俗話說:巧婦難為無米之炊。JVM 功能雖然強大,但還是需要為它提供 .class 檔案。

但是僅靠 JVM 是無法完成一次編譯,到處執行的。它需要一個基本的類庫,比如怎麼操作檔案、怎麼連線網路、怎麼教你出拳(小李已瘋)等。而 Java 體系會一次性將 JVM 執行所需的類庫都傳遞給它。JVM 標準加上基本類庫就組成了 Java 的執行環境,就是 JRE (Java Runtime Enviroment)

JVM + 基本類庫 = JRE

那 JDK 又是什麼呢?

JDK 全稱 Java Development Kit,Kit 是裝備的意思。所以 JDK 不僅包含 JRE,還有一些小工具,比如 javac、java、jar等。

JRE + javac/java/jar 等指令工具 = JDK

JVM、JRE、JDK 它們三者之間的關係,可以用一個包含關係表示。

  • JDK > JRE > JVM

Java 虛擬機器規範和 Java 語言規範的關係

從廣義上來講,JVM 是一種規範,它是最為官方、準確的文件;狹義上來講,由於我們使用 Hotspot 更多一些,所以我們在談到這個概念時,會將他們等同起來。

如果再加我們平常使用的 Java 語言,可以得到下面一張圖。

左邊是 Java 虛擬機器規範,為位元組碼的解析提供一個環境。右邊是 Java 語法規範,比如 switch、for、泛型、lambda 等相關的程式,最終都會編譯成位元組碼。而位元組碼是連結左右兩部分的橋樑。

如果 .class 檔案的規格是不變的,這兩部分是可以獨立進行優化的。But 沒有如果,現在都已經到 Java 13 了,為了支援更多的特性,肯定會增加一些位元組碼指令。

此刻優秀的小李提出了一個讓人深思的問題:

如果我不學習 JVM,會影響我寫 Java 程式碼麼?

理論上,這兩者沒有必然的聯絡。他們之間通過 .class 檔案進行互動,即使你不瞭解 JVM,也能夠寫大多數的 Java 程式碼。就像你是寫 C++ 程式碼一樣,並不需要特別深入的瞭解作業系統的底層是如何實現的。

那我還學個錘子!瞬間關了該頁面。

客官別走,還有但是沒說呢。

但是,如果你想要寫一些比較精巧、效率比較高的程式碼,就需要了解一些執行層面的知識了。瞭解 JVM,主要用在調優以及故障排查上面,你會對執行中的各種資源分配,有一個比較全面的掌控。(是不是內心還有點小期待呢!)

Java 程式碼到底是如何執行起來的

最後,我們簡單看一下 Java 程式的執行過程,瞭解下它到底是如何執行起來的。

這裡的 Java 程式是文字格式的。比如下面這段 HelloXiaoli.java,它遵循的就是 Java 語言規範。其中,我們呼叫的 System.out 等模組,就是 JRE 提供的類庫。

通過 JDK 的工具 javac 進行編譯後,就會產生 HelloWorld 的位元組碼。

1javac HelloXiaoli.java

Java 位元組碼是溝通 JVM 和 Java 程式的橋樑,下面使用 javap 來看一下位元組碼到底長什麼樣子。javap基本使用

javap -verbose HelloXiaoli.class
0 getstatic #2 <java/lang/System.out>
3 ldc #3 <Hello Xiaoli>
5 invokevirtual #4 <java/io/PrintStream.println>
8 return

Java 虛擬機器採用基於棧的架構(為什麼基於棧的架構詳見:JVM 體系結構與工作方式),其指令由操作碼和運算元組成。這些位元組碼指令,就叫做 opcode。其中,getstatic、ldc、invokeevirtual、return 等,就是 opcode。

我們繼續使用 hexdump 看一下位元組碼的二進位制內容hexdump 命令

b2 00 02 12 03 b6 00 04 b1

我們可以看一下它們的對應關係。JVM 位元組碼對照表

0xb2   getstatic       獲取靜態欄位的值
0x12   ldc             常量池中的常量值入棧
0xb6   invokevirtual   執行時方法繫結呼叫方法
0xb1   return          void 函式返回

opcode 是一個位元組的長度(0~255),意味著指令集的操作碼個數不能超過 256 條。緊跟在 opcode 後面的是被運算元。比如 b2 00 02,就代表了 getstatic #2 。

JVM 就是靠解析這些 opcode 和 運算元來完成程式的執行的,當我們使用 Java 命令執行 .class 檔案的時候,實際上就相當於啟動了一個 JVM 程序。

JVM 會翻譯這些位元組碼,它有兩種執行方式:

  • 解釋執行,將 opcode + 運算元翻譯成機器程式碼;
  • JIT,即時編譯,它會在一定條件下將位元組碼翻譯成機器碼之後再執行。

即時編譯器與直譯器的區別?

.class 檔案會被載入、存放到 metaspace 中,等待被呼叫,這裡會有一個類載入器的概念。

JVM 的程式執行,都是在棧上完成的,這和其他普通程式的執行是類似的,分為堆和棧。比如我們程式執行到了 main 方法,就會給它分配一個棧幀。當推出方法體時,會彈出相應的棧幀。其實,大多數字節碼指令,就是不斷的對棧幀進行操作。

而其它大塊資料,是存放在堆上的。Java 在記憶體劃分上會更為細緻,關於這些概念,會在後面的章節中詳細介紹。

我們看下面的圖,JVM 部分是我們系列需要講解的部分。

小結

上面講了這麼多,讓我們再回頭看看面試官提問的三個問題。

  • 為什麼 Java 研發系統需要 JVM?

因為 Java 是一門抽象的語言,並且有自動記憶體管理機制。而作業系統無法去進行自動垃圾回收等操作,所以就有了虛擬機器。虛擬機器可以對位元組碼載入、自動垃圾回收、併發等。而 JVM 只是一個規範,定義了 .class 檔案的結構、載入機制、資料儲存、執行時棧等諸多內容,最常用的 JVM 實現就是 Hotspot。

  • 你對 JVM 的執行原理了解多少?

JVM 的生命週期是和 Java 程式的執行一樣,當程式執行結束,JVM 例項也就跟著消失了。具體的執行原理,會在後續文章中詳細介紹,請關注小李哦!

  • 我們寫的 Java 程式碼到底是如何執行起來的?

Java 程式通過 javac 編譯成 .class 檔案,然後虛擬機器將其載入到元資料區,執行引擎將會通過混合模式執行這些位元組碼。執行時,會翻譯成作業系統相關的函式。

過程如下:Java 檔案->編譯器->位元組碼->JVM->機器碼

總結

本篇文章從三個角度瞭解了 JVM 在 Java 研發體系中的位置,並以一個簡單的程式,看了下一個 Java 程式的執行過程。

我們說的 JVM,狹義上指的就是 HotSpot。如果沒有特殊說明,我們都以 HotSpot 為準。

我們知道 Java 之所以跨平臺,就是由於 JVM 的存在。Java 的位元組碼,是溝通 Java 語言與 JVM 的橋樑,同時也是溝通 JVM 與作業系統的橋樑。

JVM 是一個非常小的集合,我們常說的 Java 執行時環境,也就是 JRE 包含 JVM 和一部分基礎類庫。如果加上我們常用的一些開發工具,就構成了整個 JDK。

Java 虛擬機器棧採用基於棧的架構,有比較豐富的 opcode。這些位元組碼可以解釋執行,也可以編譯成機器碼,執行在底層硬體上,可以說 JVM 是一種混合執行的策略。

留兩道思考題給大家:

  • 棧上都會有哪些資料?
  • 垃圾回收會發生在什麼地方?

思考題我會在後面的章節為大家一一解答。

參考

http://pc-shop.xiaoe-tech.com/appcCrwMYBx6232/video_details?id=v_5e14662379d00_UAifIZpt

推薦閱讀

JVM 體系結構與工作方式

學習反射看這一篇就夠了