Java為啥能跨平臺(tái)(原因解析)JDK/JRE/JVM的關(guān)系說(shuō)明
Java為啥能跨平臺(tái)?JDK/JRE/JVM的關(guān)系?
JVM到底是個(gè)啥?Java為啥能跨平臺(tái)、一次編譯處處運(yùn)行?JDK、JRE、JVM三者到底啥關(guān)系?JVM又是怎么把我們寫(xiě)的代碼跑起來(lái)的?
一、JVM是什么
JVM 全稱(chēng) Java Virtual Machine(Java虛擬機(jī)),本質(zhì)就是Java程序的運(yùn)行環(huán)境——它不直接和硬件打交道,而是介于Java代碼和操作系統(tǒng)之間的“中間層”。
我們寫(xiě)的 .java 源代碼,經(jīng)過(guò)編譯器(javac)編譯后,會(huì)變成 .class 文件——這是Java的二進(jìn)制字節(jié)碼文件(不是操作系統(tǒng)能直接執(zhí)行的機(jī)器指令)。而JVM的核心作用,就是讀取并執(zhí)行這份 .class 文件,讓代碼真正跑起來(lái)。
二、Java 的跨平臺(tái)特性:一次編譯,處處運(yùn)行
這是Java最核心的優(yōu)勢(shì)之一,而背后的關(guān)鍵,就是JVM的“適配性設(shè)計(jì)”——不同操作系統(tǒng)有不同版本的JVM,但它們都能運(yùn)行同一份 .class 文件。
具體邏輯很簡(jiǎn)單:
- 你寫(xiě)的Java代碼,只需要編譯一次,就能生成統(tǒng)一格式的
.class字節(jié)碼文件(不管你是在Windows、Mac還是Linux上編譯,生成的.class文件完全一樣); - Windows、Mac、Linux等不同操作系統(tǒng),都會(huì)提供對(duì)應(yīng)的JVM版本(比如Windows版JVM、Mac版JVM);
- 當(dāng)你在某個(gè)操作系統(tǒng)上運(yùn)行Java程序時(shí),對(duì)應(yīng)的JVM會(huì)把
.class字節(jié)碼,“翻譯”成該操作系統(tǒng)能直接執(zhí)行的機(jī)器指令; - 操作系統(tǒng)執(zhí)行這些機(jī)器指令,最終完成程序運(yùn)行。
舉個(gè)例子:你在Windows上編譯好的 .class 文件,復(fù)制到Mac上,Mac版JVM會(huì)自動(dòng)把它翻譯成Mac系統(tǒng)的指令,無(wú)需重新編譯——這就是“一次編譯,處處運(yùn)行”的本質(zhì)。
不過(guò)要提一句:Docker出現(xiàn)后,Java的跨平臺(tái)優(yōu)勢(shì)被削弱了(Docker本身就是“容器化跨平臺(tái)”),但JVM作為Java軟件的核心運(yùn)行環(huán)境,依然是不可或缺的。
三、JDK、JRE、JVM:三者的關(guān)系
很多人會(huì)把這三個(gè)概念搞混,其實(shí)它們是“包含與被包含”的關(guān)系,用一句話就能概括:JDK > JRE > JVM。

1. JVM(Java Virtual Machine)
就是我們前面說(shuō)的運(yùn)行 .class 文件的虛擬機(jī)——只負(fù)責(zé)運(yùn)行 .class 字節(jié)碼文件,是Java程序能跑起來(lái)的基礎(chǔ)。但它光自己不行,還需要依賴(lài)類(lèi)庫(kù)才能工作。
2. JRE(Java Runtime Environment)
JRE = JVM + Java核心類(lèi)庫(kù)(比如 java.lang.String、java.util.ArrayList 這些基礎(chǔ)類(lèi))。
因?yàn)榇a執(zhí)行需調(diào)用很多 Java SE 的類(lèi)庫(kù)(比如字符串處理、集合操作),所以 JRE 在 JVM 的基礎(chǔ)上自帶了這些類(lèi)庫(kù)。
3. JDK(Java Development Kit)
但我們不是一上來(lái)就寫(xiě) .class 文件的,我們寫(xiě)的是 .java 文件,而 .java 文件需要編譯成 .class 文件。該編譯工作由 JDK 提供的工具完成 ——JDK 其實(shí)就是 JRE 加上編譯、調(diào)試、打包等各種開(kāi)發(fā)工具的集合。
JDK = JRE + 編譯、調(diào)試、打包等開(kāi)發(fā)工具(比如javac編譯器、java運(yùn)行命令、jar打包工具、jdb調(diào)試軟件等)。
我們寫(xiě)Java代碼時(shí),不是一上來(lái)就寫(xiě) .class 文件的,我們寫(xiě)的是 .java 文件,而 .java 文件需要用 javac 命令編譯 .java 文件成 .class 文件,用 jar 命令打包程序——這個(gè)編譯工作由 JDK 提供的工具完成 ,JDK 其實(shí)就是 JRE 加上編譯、調(diào)試、打包等各種開(kāi)發(fā)工具的集合。
四、JVM運(yùn)行代碼流程
JVM運(yùn)行 .class 字節(jié)碼的過(guò)程,核心依賴(lài)5個(gè)核心組件:類(lèi)加載系統(tǒng)、運(yùn)行時(shí)數(shù)據(jù)區(qū)、執(zhí)行引擎、本地方法接口(JNI)、本地方法庫(kù)。整個(gè)流程就像“工廠加工產(chǎn)品”,一步步把字節(jié)碼變成可執(zhí)行的指令。

1. 第一步:類(lèi)加載框架
.class 文件里包含了類(lèi)的所有信息(類(lèi)名、父類(lèi)、接口、字段、方法、訪問(wèn)修飾符等),類(lèi)加載系統(tǒng)的作用,就是把這些 .class 文件加載到JVM的“內(nèi)存區(qū)域”(運(yùn)行時(shí)數(shù)據(jù)區(qū))。
2. 第二步:運(yùn)行時(shí)數(shù)據(jù)區(qū)
加載后的類(lèi)、代碼執(zhí)行時(shí)產(chǎn)生的數(shù)據(jù)(比如對(duì)象、變量、方法調(diào)用信息),都會(huì)存放在這里。它相當(dāng)于JVM的“工作內(nèi)存”,主要分為5個(gè)部分:
| 內(nèi)存區(qū)域 | 核心作用 |
|---|---|
| 方法區(qū)(Method Area) | 存放已加載類(lèi)的元數(shù)據(jù)(類(lèi)名、父類(lèi)、接口、字段、方法等),比如你定義的 User 類(lèi)信息就存在這 |
| 堆(Heap) | 存放代碼中 new 出來(lái)的對(duì)象(比如 User user = new User()),是JVM中最大的內(nèi)存區(qū)域 |
| 虛擬機(jī)棧(VM Stack) | 存放方法調(diào)用的信息(比如調(diào)用 main() 方法、add() 方法時(shí),會(huì)創(chuàng)建“棧幀”存在這里),方法執(zhí)行完棧幀就銷(xiāo)毀 |
| 程序計(jì)數(shù)器(Program Counter Register) | 記錄當(dāng)前線程執(zhí)行到哪一行字節(jié)碼指令(多線程時(shí),切換線程后能恢復(fù)執(zhí)行位置,避免亂序) |
| 本地方法棧(Native Method Stack) | 存放Java調(diào)用的“本地方法”(用C/C++寫(xiě)的方法,比如Java底層的IO操作、線程管理)的調(diào)用信息 |
3. 第三步:執(zhí)行引擎
.class 文件里的字節(jié)碼,只是JVM能識(shí)別的“指令集規(guī)范”,不是操作系統(tǒng)能直接執(zhí)行的機(jī)器指令——這時(shí)候就需要執(zhí)行引擎來(lái)“翻譯”。
執(zhí)行引擎的核心工作:
- 讀取運(yùn)行時(shí)數(shù)據(jù)區(qū)里的字節(jié)碼指令;
- 把字節(jié)碼指令“解釋”(或編譯)成當(dāng)前操作系統(tǒng)能識(shí)別的機(jī)器指令;
- 把機(jī)器指令交給CPU執(zhí)行。
這里要提一句:Java不是純“解釋執(zhí)行”,還會(huì)用到JIT(Just-In-Time 即時(shí)編譯器)——熱點(diǎn)代碼(頻繁執(zhí)行的代碼)會(huì)被JIT編譯成機(jī)器指令緩存起來(lái),下次執(zhí)行直接用,不用重復(fù)解釋?zhuān)@樣能提升性能。Java 編譯器把 .java 文件編譯為字節(jié)碼文件后,JVM 執(zhí)行引擎將字節(jié)碼指令解釋為對(duì)應(yīng)操作系統(tǒng)能運(yùn)行的指令并執(zhí)行,這也就是為什么說(shuō) Java 是編譯和解釋共存的語(yǔ)言。
4. 第四步:本地方法接口(JNI)+ 本地方法庫(kù)
用C/C++寫(xiě)的“本地方法”,存放在本地方法庫(kù)中。就是Java代碼執(zhí)行時(shí),會(huì)遇到一些“Java做不了”的操作(比如操控硬件、底層IO、線程調(diào)度),這些操作都
前文也提到了,本地辦法執(zhí)行時(shí),相關(guān)信息會(huì)放到運(yùn)行時(shí)數(shù)據(jù)區(qū)的本地方法棧里。
到此這篇關(guān)于java多線程的文章就介紹到這了,更多相關(guān)java多線程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot自動(dòng)掃描添加的BeanDefinition源碼實(shí)例詳解
這篇文章主要給大家介紹了關(guān)于springboot自動(dòng)掃描添加的BeanDefinition的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-02-02
javaSE,javaEE,javaME的區(qū)別小結(jié)
本篇文章小編就為大家簡(jiǎn)單說(shuō)說(shuō)JavaSE、JavaEE、JavaME三者之間的區(qū)別,需要的朋友可以過(guò)來(lái)參考下,感興趣的小伙伴們可以參考一下2023-08-08
Maven項(xiàng)目繼承實(shí)現(xiàn)過(guò)程圖解
這篇文章主要介紹了Maven項(xiàng)目繼承實(shí)現(xiàn)過(guò)程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
Java IO流之原理分類(lèi)與節(jié)點(diǎn)流文件操作詳解
流(Stream)是指一連串的數(shù)據(jù)(字符或字節(jié)),是以先進(jìn)先出的方式發(fā)送信息的通道,數(shù)據(jù)源發(fā)送的數(shù)據(jù)經(jīng)過(guò)這個(gè)通道到達(dá)目的地,按流向區(qū)分為輸入流和輸出流2021-10-10
Springboot+Mybatis實(shí)現(xiàn)分頁(yè)加條件查詢(xún)功能
這篇文章主要為大家詳細(xì)介紹了Springboot+Mybatis實(shí)現(xiàn)分頁(yè)加條件查詢(xún),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
Java數(shù)據(jù)結(jié)構(gòu)學(xué)習(xí)之樹(shù)
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)學(xué)習(xí)之樹(shù),文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java數(shù)據(jù)結(jié)構(gòu)的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-05-05
Java String不可變性實(shí)現(xiàn)原理解析
這篇文章主要介紹了Java String不可變性實(shí)現(xiàn)原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
解決Spring Cloud feign GET請(qǐng)求無(wú)法用實(shí)體傳參的問(wèn)題
這篇文章主要介紹了解決Spring Cloud feign GET請(qǐng)求無(wú)法用實(shí)體傳參的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01

