我們一起聊聊JVM優(yōu)化:JVM概述
一、什么是JVM
JVM是Java Virtual Machine(Java虛擬機(jī))的縮寫,JVM是一種用于計(jì)算設(shè)備的規(guī)范,它是一個(gè)虛構(gòu)出來(lái)的計(jì)算 機(jī),是通過在實(shí)際的計(jì)算機(jī)上仿真模擬各種計(jì)算機(jī)功能來(lái)實(shí)現(xiàn)的。
二、JVM與操作系統(tǒng)
Java 是一門抽象程度特別高的語(yǔ)言,提供了自動(dòng)內(nèi)存管理等一系列的特性。這些特性直接在操作系統(tǒng)上實(shí)現(xiàn)是不太 可能的,所以就需要 JVM 進(jìn)行一番轉(zhuǎn)換。
從圖中可以看到,有了 JVM 這個(gè)抽象層之后,Java 就可以實(shí)現(xiàn)跨平臺(tái)了。JVM 只需要保證能夠正確執(zhí)行 .class 文 件,就可以運(yùn)行在諸如 Linux、Windows、MacOS 等平臺(tái)上了。 而 Java 跨平臺(tái)的意義在于一次編譯,處處運(yùn)行,能夠做到這一點(diǎn) JVM 功不可沒。比如我們?cè)?Maven 倉(cāng)庫(kù)下載同一 版本的 jar 包就可以到處運(yùn)行,不需要在每個(gè)平臺(tái)上再編譯一次。 現(xiàn)在的一些 JVM 的擴(kuò)展語(yǔ)言,比如 Clojure、JRuby、Groovy 等,編譯到最后都是 .class 文件,Java 語(yǔ)言的維護(hù) 者,只需要控制好 JVM 這個(gè)解析器,就可以將這些擴(kuò)展語(yǔ)言無(wú)縫的運(yùn)行在 JVM 之上了。
我們用一句話概括 JVM 與操作系統(tǒng)之間的關(guān)系:JVM 上承開發(fā)語(yǔ)言,下接操作系統(tǒng),它的中間接口就是字節(jié)碼。
三、JVM、JRE、JDK 的關(guān)系
JVM 是 Java 程序能夠運(yùn)行的核心。但是需要注意,JVM 自己什么也干不了,你需要給它提供生產(chǎn)原料(.class 文 件) 。
僅僅是 JVM,是無(wú)法完成一次編譯,處處運(yùn)行的。它需要一個(gè)基本的類庫(kù),比如怎么操作文件、怎么連接網(wǎng)絡(luò)等。 而 Java 體系很慷慨,會(huì)一次性將 JVM 運(yùn)行所需的類庫(kù)都傳遞給它。JVM 標(biāo)準(zhǔn)加上實(shí)現(xiàn)的一大堆基礎(chǔ)類庫(kù),就組成 了 Java 的運(yùn)行時(shí)環(huán)境,也就是我們常說的 JRE(Java Runtime Environment) 對(duì)于 JDK 來(lái)說,就更龐大了一些。除了 JRE,JDK 還提供了一些非常好用的小工具,比如 javac、java、jar 等。它 是 Java 開發(fā)的核心,讓外行也可以煉劍!
我們也可以看下 JDK 的全拼,Java Development Kit。我非常怕 kit(裝備)這個(gè)單詞,它就像一個(gè)無(wú)底洞,預(yù)示著 你永無(wú)休止的對(duì)它進(jìn)行研究。JVM、JRE、JDK 它們?nèi)咧g的關(guān)系,可以用一個(gè)包含關(guān)系表示。
四、Java虛擬機(jī)規(guī)范和 Java 語(yǔ)言規(guī)范的關(guān)系
左半部分是 Java 虛擬機(jī)規(guī)范,其實(shí)就是為輸入和執(zhí)行字節(jié)碼提供一個(gè)運(yùn)行環(huán)境。右半部分是我們常說的 Java 語(yǔ)法 規(guī)范,比如 switch、for、泛型、lambda 等相關(guān)的程序,最終都會(huì)編譯成字節(jié)碼。而連接左右兩部分的橋梁依然是 Java 的字節(jié)碼。
如果 .class 文件的規(guī)格是不變的,這兩部分是可以獨(dú)立進(jìn)行優(yōu)化的。但 Java 也會(huì)偶爾擴(kuò)充一下 .class 文件的格式, 增加一些字節(jié)碼指令,以便支持更多的特性。
我們可以把 Java 虛擬機(jī)可以看作是一臺(tái)抽象的計(jì)算機(jī),它有自己的指令集以及各種運(yùn)行時(shí)內(nèi)存區(qū)域,學(xué)過《計(jì)算 機(jī)組成結(jié)構(gòu)》的同學(xué)會(huì)在課程的后面看到非常多的相似性。
最后,我們簡(jiǎn)單看一下一個(gè) Java 程序的執(zhí)行過程,它到底是如何運(yùn)行起來(lái)的
這里的 Java 程序是文本格式的。比如下面這段 HelloWorld.java,它遵循的就是 Java 語(yǔ)言規(guī)范。其中,我們調(diào)用了 System.out 等模塊,也就是 JRE 里提供的類庫(kù)。
使用 JDK 的工具 javac 進(jìn)行編譯后,會(huì)產(chǎn)生 HelloWorld 的字節(jié)碼。 我們一直在說 Java 字節(jié)碼是溝通 JVM 與 Java 程序的橋梁,下面使用 javap 來(lái)稍微看一下字節(jié)碼到底長(zhǎng)什么樣子。
Java 虛擬機(jī)采用基于棧的架構(gòu),其指令由操作碼和操作數(shù)組成。這些 字節(jié)碼指令 ,就叫作 opcode。其中, getstatic、ldc、invokevirtual、return 等,就是 opcode,可以看到是比較容易理解的。 JVM 就是靠解析這些 opcode 和操作數(shù)來(lái)完成程序的執(zhí)行的。當(dāng)我們使用 Java 命令運(yùn)行 .class 文件的時(shí)候,實(shí)際上 就相當(dāng)于啟動(dòng)了一個(gè) JVM 進(jìn)程。
然后 JVM 會(huì)翻譯這些字節(jié)碼,它有兩種執(zhí)行方式。常見的就是解釋執(zhí)行,將 opcode + 操作數(shù)翻譯成機(jī)器代碼;另 外一種執(zhí)行方式就是 JIT,也就是我們常說的即時(shí)編譯,它會(huì)在一定條件下將字節(jié)碼編譯成機(jī)器碼之后再執(zhí)行。