java運行機制之編譯期、運行期和半編譯半解釋性詳解
前言
Java 的生命周期設(shè)計之所以經(jīng)典,核心在于它采用了 “半編譯,半解釋” 的混合模式。
要真正做到“有深度”地理解 Java 的編譯期和運行期,需要深入到 前端編譯(javac) 和 后端編譯/執(zhí)行(JIT/JVM) 的底層工作流與優(yōu)化策略中。
如果從程序執(zhí)行模型來看,編程語言大致可以分為三類:
- 純編譯型語言:提前編譯為機器碼,運行速度極快,但跨平臺能力較差;
- 純解釋型語言:運行時逐行解釋執(zhí)行,靈活性強,但性能較低;
- 半編譯半解釋型語言:先編譯為中間字節(jié)碼,再由虛擬機動態(tài)執(zhí)行與優(yōu)化。
Java 并不會像 C/C++ 那樣直接編譯為特定平臺的機器碼,而是先通過 javac 編譯器將源碼編譯為 跨平臺字節(jié)碼(Bytecode),再交由 JVM(Java Virtual Machine)在不同平臺上運行。
在運行過程中,JVM 又結(jié)合了:
- 解釋執(zhí)行(Interpreter)
- 即時編譯(JIT, Just-In-Time Compiler)
兩種機制。
程序啟動初期通過解釋執(zhí)行保證快速啟動;而高頻運行的熱點代碼,則會被 JIT 編譯為本地機器碼并緩存,從而獲得接近 C/C++ 的運行性能。
也正因為如此,Java 才真正實現(xiàn)了著名的:
Write Once, Run Anywhere(一次編寫,到處運行)
本文從 編譯期、運行期、JVM 執(zhí)行機制、JIT 優(yōu)化以及三種語言執(zhí)行模型 等多個維度,簡單梳理 Java 的運行機制與底層原理。
一、 編譯期
Java 的編譯期通常指的是前端編譯,即由 javac 編譯器將 .java 源代碼文件轉(zhuǎn)化為 .class 字節(jié)碼文件的過程。
這個階段的核心目標是:校驗語言規(guī)范、轉(zhuǎn)換語法結(jié)構(gòu),但不做深度的性能優(yōu)化。
1.1 核心機制
編譯期的主要工作不是優(yōu)化性能,而是將 .java 源碼轉(zhuǎn)換為符合 JVM 規(guī)范的 .class 字節(jié)碼。
javac的運行機制如下
解析與填充符號表 :
將源代碼字符流轉(zhuǎn)變?yōu)闃擞洠═oken),構(gòu)造出抽象語法樹(AST)。此時如果漏掉分號,就會報語法錯誤。
注解處理 :
編譯器允許自定義的注解處理器去掃描和修改 AST。
Lombok 就是在此階段大顯身手,強行插入
getter/setter節(jié)點,隨后編譯器基于修改后的 AST 重新編譯。語義分析:
檢查變量是否聲明、類型是否匹配。進行常量折疊(Constant Folding)(如
int a = 1 + 2;直接變成3)。字節(jié)碼生成與解語法糖 :
JVM 不認識語法糖(如增強 for、變長參數(shù)),
javac會將其還原。例如泛型的類型擦除(Type Erasure)就在此發(fā)生,
List<String>會被擦除為List。最終,AST 被轉(zhuǎn)換為字節(jié)碼指令寫入
.class文件。

1.2 方法重載
靜態(tài)類型檢查: 編譯器嚴格按照變量的聲明類型進行校驗。
如果代碼中有多個同名方法(方法重載 Overload),該調(diào)哪個?
- 決策邏輯: 編譯器完全根據(jù)傳入?yún)?shù)的聲明類型(靜態(tài)類型)來決定。
- 結(jié)果: 編譯器一旦匹配到最合適的重載方法,就會將這個確定的符號引用硬編碼寫進
.class文件的字節(jié)碼中。重載的決斷,在編譯期就已經(jīng)徹底鎖死。
class Animal {}
class Dog extends Animal {}
public class Test {
// 【方法重載 Overload】
public void feed(Animal a) { System.out.println("Feeding an animal..."); }
public void feed(Dog d) { System.out.println("Feeding a dog..."); }
public static void main(String[] args) {
// 靜態(tài)類型: Animal, 實際類型: Dog
Animal myPet = new Dog();
Test test = new Test();
// 提問:這里會輸出什么?
test.feed(myPet);
}
}
底層執(zhí)行流程:
- 編譯期: 編譯器看到
test.feed(myPet)。它檢查myPet的靜態(tài)類型(Animal),于是匹配到了feed(Animal a),并將該符號引用寫入字節(jié)碼。 - 運行期(實干家): JVM 執(zhí)行時,看到字節(jié)碼指令要求調(diào)用
feed(Animal a),直接執(zhí)行。它不會在此刻因為myPet實際上是個Dog而去反悔重選。 - 最終輸出:
Feeding an animal...
二、 運行期
運行期是 Java 最為復(fù)雜和體現(xiàn)技術(shù)深度的部分。 當(dāng)類加載器將 .class 文件讀入內(nèi)存后,JVM(java虛擬機)開始接管一切。
運行期(由 JVM 和 JIT 編譯器主導(dǎo))它生存在一個動態(tài)的世界里,眼中只有堆內(nèi)存里等號右邊的真實對象。
2.1 核心機制
- 動態(tài)類加載機制:
- 字節(jié)碼被加載進方法區(qū)(Metaspace),在“解析”階段,JVM 會將常量池中的符號引用替換為直接引用(內(nèi)存指針)。
- 對于多態(tài)方法,這個解析會推遲到真正運行時才進行(動態(tài)綁定)。
- 混合模式 (Interpreter + JIT):
- 解釋器 (Interpreter):
- 程序剛啟動時,JVM 的解釋器會逐條讀取字節(jié)碼,將其翻譯成當(dāng)前操作系統(tǒng)認識的機器碼并執(zhí)行。
- 作用: 保證程序能瞬間啟動,不需要等待漫長的全量編譯時間。
- 即時編譯器 (JIT - Just-In-Time):
- 性能優(yōu)化關(guān)鍵:當(dāng)它發(fā)現(xiàn)某一段代碼(比如某個方法、某個大循環(huán))被極其頻繁地調(diào)用時,它會把這段代碼標記為“熱點代碼(Hot Spot)”。
- JIT 會將其編譯為本地機器碼(C1 編譯器做局部優(yōu)化,C2 編譯器做激進優(yōu)化)。
- 下次再走到這段代碼時,JVM 直接執(zhí)行緩存的機器碼,速度提升。
- 解釋器 (Interpreter):

2.2 多態(tài)
運行期的動態(tài)特性開始顯現(xiàn):如何實現(xiàn)多態(tài)(方法重寫 Override)?
- 決策邏輯: JVM 無視變量的聲明類型,直接去堆內(nèi)存中找對象的實際類型。
- 執(zhí)行過程: JVM 查該實際類型對象的虛方法表(vtable)。如果在子類中找到了重寫的方法,就執(zhí)行子類的邏輯;否則順著繼承鏈往上找。這就是 Java 實現(xiàn)多態(tài)的底層基石。
// 1. 定義頂層父類
class Animal {
public void speak() {
System.out.println("Animal vtable: [speak() -> Animal.speak()] : 動物發(fā)出未知的叫聲");
}
}
// 2. 定義子類 Dog:【重寫】了 speak 方法
class Dog extends Animal {
@Override
public void speak() {
System.out.println("Dog vtable: [speak() -> Dog.speak()] : 汪汪汪!(執(zhí)行了子類邏輯)");
}
}
// 3. 定義子類 Bird:【沒有重寫】 speak 方法
class Bird extends Animal {
public void fly() {
System.out.println("鳥兒在飛翔...");
}
// 注意:這里沒有重寫 speak() 方法
}
// 4. 測試主類
public class VTableTest {
public static void main(String[] args) {
System.out.println("====== 多態(tài)與虛方法表測試開始 ======\n");
/* * 場景 :JVM 無視聲明類型 (Animal),直接找實際類型 (Dog)
*/
Animal myDog = new Dog(); // 聲明類型: Animal, 實際類型: Dog
System.out.print("調(diào)用 myDog.speak() ---> ");
// JVM 去堆內(nèi)存找 myDog 的實際對象頭,查 Dog 類的 vtable
// 在 Dog 類的 vtable 中找到了重寫的 speak(),直接執(zhí)行!
myDog.speak();
System.out.println("--------------------------------------------------");
/* * 場景 :順著繼承鏈往上找
*/
Animal myBird = new Bird(); // 聲明類型: Animal, 實際類型: Bird
System.out.print("調(diào)用 myBird.speak() ---> ");
// JVM 去堆內(nèi)存找 myBird 的實際對象頭,查 Bird 類的 vtable
// 發(fā)現(xiàn) Bird 類并沒有重寫 speak(),查表落空!
// 觸發(fā)機制:順著繼承鏈往上找,查到 Animal 類的 vtable 中有 speak(),執(zhí)行父類邏輯!
myBird.speak();
System.out.println("\n====== 測試結(jié)束 ======");
}
}
代碼運行時底層流程:
1. 當(dāng)執(zhí)行 myDog.speak() 時:
- 編譯期:
javac編譯器只看左邊,確認Animal類里確實有speak()方法,于是允許編譯通過,并在字節(jié)碼中寫入一條invokevirtual Animal.speak的指令。 - 運行期: 執(zhí)行到這條指令時,JVM 的動態(tài)特性啟動。它根本不管字節(jié)碼里寫的是
Animal.speak。它直接去內(nèi)存里找到那個被new出來的真正對象(Dog實例)。 - 查表: JVM 提取
Dog實例的對象頭信息,定位到Dog類的虛方法表(vtable)。它發(fā)現(xiàn)表里的speak指針已經(jīng)被替換成了Dog自己實現(xiàn)的方法地址。于是,輸出“汪汪汪”。
2. 當(dāng)執(zhí)行 myBird.speak() 時:
- 編譯期: 同樣順利通過。
- 運行期: JVM 找到內(nèi)存里真正的
Bird實例。 - 查表與回溯: JVM 查看
Bird類的虛方法表。但是,因為Bird類沒有寫@Override public void speak(),所以在這個表里,speak指針依然原封不動地指向著父類Animal的speak方法的地址。 - 結(jié)果: 于是 JVM 只能“順藤摸瓜”,執(zhí)行了父類的方法,輸出“動物發(fā)出未知的叫聲”。這也就是你提到的:“否則順著繼承鏈往上找”。
三、java代碼的編譯和運行
3.1 編譯期:Java 源碼 → 跨平臺字節(jié)碼
對應(yīng)圖中綠色區(qū)域,是脫離 JVM 的前置編譯環(huán)節(jié),核心是把人類可讀的 Java 源碼,轉(zhuǎn)為 JVM 可識別的統(tǒng)一標準格式。
- 輸入:Java 源碼文件(
.java) - 核心處理:通過**
javac**等 Java 編譯器完成 4 步標準化操作:- 解析源碼生成抽象語法樹(AST)、填充符號表
- 注解處理(如 Lombok 等插件的攔截增強)
- 語義分析(常量折疊、語法校驗,確保代碼邏輯合法)
- 生成字節(jié)碼、解語法糖(如 foreach、泛型擦除等簡化語法的還原)
- 輸出:Java 字節(jié)碼文件(
.class)- 關(guān)鍵特性:字節(jié)碼不綁定任何操作系統(tǒng)、CPU 架構(gòu),是 JVM 專屬的跨平臺統(tǒng)一指令集,是 Java 實現(xiàn)跨平臺能力的核心基礎(chǔ)。
3.2 運行期:字節(jié)碼 → 機器碼 → 硬件執(zhí)行
對應(yīng)圖中紅色 JVM 區(qū)域,是代碼真正執(zhí)行的核心環(huán)節(jié),全流程在 JVM 中完成,最終落地到操作系統(tǒng)和底層硬件。
完整執(zhí)行鏈路如下:
類加載環(huán)節(jié):
.class字節(jié)碼通過文件系統(tǒng) / 網(wǎng)絡(luò)進入 JVM,由類加載器完成加載→驗證→準備→解析→初始化全流程,將字節(jié)碼加載到 JVM 內(nèi)存中,生成可執(zhí)行的類結(jié)構(gòu)。
混合模式執(zhí)行(JVM 核心設(shè)計)
類加載完成后,JVM 采用「解釋執(zhí)行 + JIT 編譯」的雙路徑執(zhí)行,平衡啟動速度與峰值性能:
解釋執(zhí)行:由 Java 解釋器逐行翻譯字節(jié)碼為機器碼,翻譯完成立即執(zhí)行。優(yōu)勢是啟動快、無編譯等待開銷,保證跨平臺兼容性,是程序啟動初期的主要執(zhí)行方式。
JIT 即時編譯:運行時 JVM 會識別高頻執(zhí)行的「熱點代碼」(如循環(huán)、頻繁調(diào)用的方法),由 C1/C2 JIT 編譯器完成深度優(yōu)化(方法內(nèi)聯(lián)、逃逸分析、標量替換等),一次性編譯為本地機器碼并緩存,后續(xù)執(zhí)行直接調(diào)用,無需重復(fù)翻譯,峰值性能無限接近 C/C++ 等純編譯型語言。
最終落地:解釋器 / JIT 生成的機器碼,交由操作系統(tǒng)調(diào)度,最終在底層 CPU 硬件上完成執(zhí)行。

四、java的語言特性
4.1 純編譯型語言
提前全量靜態(tài)編譯,直接生成目標平臺專屬機器碼,運行時無額外翻譯,CPU可直接執(zhí)行,無中間層開銷。
- 開發(fā)階段:編寫源代碼(如C的
.c、C++的.cpp文件)。 - 全量編譯階段:通過編譯器(GCC/Clang/MSVC)完成「預(yù)編譯→編譯→匯編→鏈接」,生成當(dāng)前操作系統(tǒng)+CPU架構(gòu)專屬的可執(zhí)行文件(Windows的
.exe、Linux的ELF等),內(nèi)含純機器碼。 - 運行階段:操作系統(tǒng)直接加載可執(zhí)行文件到內(nèi)存,CPU逐條執(zhí)行機器碼,全程無翻譯。
核心優(yōu)缺點:
| 優(yōu)點 | 缺點 |
|---|---|
| 運行性能最快,無運行時開銷 | 跨平臺性極差,需按平臺單獨編譯 |
| 內(nèi)存完全可控,無額外運行時內(nèi)存占用 | 編譯耗時久,代碼改動需重新編譯 |
| 可做極致靜態(tài)編譯優(yōu)化 | 開發(fā)門檻高,需手動處理內(nèi)存、指針等底層細節(jié) |
代表語言與適用場景
- 代表語言:C、C++、Rust、Go、Swift、匯編
- 適用場景:對性能、延遲要求極高的領(lǐng)域,如操作系統(tǒng)內(nèi)核、嵌入式開發(fā)、游戲引擎、高頻交易系統(tǒng)。
4.2 純解釋型語言
無提前全量編譯,運行時由解釋器逐行翻譯源代碼為機器碼并執(zhí)行,不生成獨立可執(zhí)行文件,也不緩存翻譯結(jié)果,靈活性高但性能犧牲大。
- 開發(fā)階段:編寫源代碼(如早期Python的
.py、Shell的.sh文件),直接分發(fā)源碼。 - 運行階段:目標機器需安裝對應(yīng)解釋器;解釋器逐行讀取源碼,實時翻譯為機器碼并執(zhí)行,不緩存翻譯結(jié)果。
核心優(yōu)缺點
| 優(yōu)點 | 缺點 |
|---|---|
| 極致源代碼跨平臺,同一份代碼有解釋器即可運行 | 運行性能極差,通常比純編譯型慢10-100倍 |
| 開發(fā)效率極高,無需編譯、改完即跑 | 必須依賴解釋器,無對應(yīng)環(huán)境無法執(zhí)行 |
| 動態(tài)性拉滿,運行時可隨意修改代碼邏輯、變量類型 | 無法提前靜態(tài)優(yōu)化,運行時才暴露語法、類型錯誤 |
代表語言與適用場景
- 代表語言(經(jīng)典純解釋實現(xiàn)):早期Python、早期JavaScript、Shell腳本、BASIC
- 補充說明:現(xiàn)代主流腳本語言已脫離純解釋模型(如CPython預(yù)編譯為
.pyc、V8引入JIT),本質(zhì)為半編譯半解釋。 - 適用場景:自動化運維腳本、快速原型驗證、網(wǎng)頁前端交互、輕量工具開發(fā)。
4.3 半編譯半解釋型語言
為解決「編譯型跨平臺差、解釋型性能差」的矛盾誕生,結(jié)合兩者優(yōu)勢:先通過前端編譯器將源碼編譯為跨平臺中間碼(字節(jié)碼),運行時由目標平臺虛擬機通過「解釋執(zhí)行+即時編譯」混合模式轉(zhuǎn)為機器碼執(zhí)行,實現(xiàn)“一次編寫,到處運行”。
分為編譯期和運行期兩大階段:
編譯期(一次編寫的核心)
開發(fā)階段:編寫Java源代碼(
.java文件)。前端編譯:通過
javac編譯器將源碼一次性編譯為跨平臺字節(jié)碼文件(.class)。- 關(guān)鍵特性:字節(jié)碼不綁定操作系統(tǒng)、CPU架構(gòu),是JVM專屬統(tǒng)一指令集,同一份字節(jié)碼在所有平臺一致,是跨平臺核心基礎(chǔ)。
運行期(到處運行的核心)
目標機器需安裝對應(yīng)平臺的JVM虛擬機(Windows/Linux/macOS均有專屬JVM,適配底層系統(tǒng)和硬件)。
類加載:JVM把
.class字節(jié)碼加載到內(nèi)存,完成驗證、準備、解析等流程。混合模式執(zhí)行(平衡啟動速度與峰值性能):
- 解釋執(zhí)行:程序啟動初期,逐行翻譯字節(jié)碼為機器碼執(zhí)行,保證快速啟動與跨平臺兼容。
- JIT即時編譯:運行時識別高頻「熱點代碼」(如循環(huán)、頻繁調(diào)用的方法),一次性全量編譯為本地機器碼并緩存,后續(xù)執(zhí)行直接調(diào)用,無需重復(fù)翻譯,峰值性能無限接近純編譯型語言。

核心優(yōu)缺點
| 優(yōu)點 | 缺點 |
|---|---|
| 真正跨平臺:同一份字節(jié)碼,有對應(yīng)JVM即可運行,無需按平臺修改、重編譯 | 有啟動開銷:JVM啟動、類加載、解釋執(zhí)行有固定開銷,不適合超短生命周期程序(如簡單腳本) |
| 高性能:JIT即時編譯優(yōu)化,熱點代碼性能接近C/C++,遠超純解釋型語言 | 強依賴運行環(huán)境:目標機器需安裝對應(yīng)版本JVM,原生無法生成獨立可執(zhí)行文件(GraalVM可實現(xiàn),非原生能力) |
| 內(nèi)存安全、開發(fā)效率高:JVM自帶GC自動回收內(nèi)存,無需手動管理,規(guī)避內(nèi)存泄漏、野指針問題 | 內(nèi)存占用更高:JVM本身、GC、運行時數(shù)據(jù)區(qū)均有固定內(nèi)存開銷 |
| 動態(tài)能力強:基于字節(jié)碼和JVM實現(xiàn)反射、動態(tài)代理,是Spring等企業(yè)級框架的核心支撐 | 底層控制能力有限:不適合操作系統(tǒng)內(nèi)核、驅(qū)動等硬件級開發(fā) |
代表語言與適用場景
- 代表語言:Java、C#、Kotlin(JVM平臺)、Scala
- 適用場景:企業(yè)級后端開發(fā)、Android開發(fā)、大數(shù)據(jù)框架(Hadoop/Spark)、中間件開發(fā),是兼顧跨平臺性、開發(fā)效率與高性能的優(yōu)選方案。
4.4 常見誤區(qū)
- 現(xiàn)代Python/JavaScript并非純解釋型語言:CPython會預(yù)編譯為
.pyc字節(jié)碼,Chrome V8、PyPy均引入JIT編譯,本質(zhì)已屬于半編譯半解釋模型。 - Go/Rust并非半編譯型語言:二者雖帶GC、協(xié)程調(diào)度等運行時能力,但均提前全量編譯為目標平臺機器碼,運行時無翻譯環(huán)節(jié),屬于純編譯型語言。
- Java并非固定半編譯半解釋模式:現(xiàn)代JVM默認「解釋+JIT」混合模式,可通過參數(shù)強制純解釋/純編譯;GraalVM支持提前將Java代碼編譯為本地機器碼,轉(zhuǎn)為純編譯型執(zhí)行。
總結(jié)
Java 的運行機制,本質(zhì)上是一套圍繞 “跨平臺 + 高性能” 而設(shè)計的動態(tài)執(zhí)行體系。
與傳統(tǒng)純編譯型語言不同,Java 并不會直接生成特定平臺的機器碼,而是采用:**“源碼 → 字節(jié)碼 → JVM → 機器碼”**的分層執(zhí)行模型。
它的本質(zhì)是:
先編譯為跨平臺字節(jié)碼,再由 JVM 動態(tài)解釋與 JIT 編譯執(zhí)行。
因此:
- 編譯型解決性能問題
- 解釋型解決跨平臺問題
- JVM/JIT 負責(zé)在兩者之間尋找平衡
最終形成了 Java 最經(jīng)典的設(shè)計哲學(xué):
一次編寫,到處運行(Write Once, Run Anywhere)
到此這篇關(guān)于java運行機制之編譯期、運行期和半編譯半解釋性詳解的文章就介紹到這了,更多相關(guān)java編譯期、運行期和半編譯半解釋性內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot整合gateway實現(xiàn)網(wǎng)關(guān)功能的示例代碼
本文主要介紹了springboot整合gateway實現(xiàn)網(wǎng)關(guān)功能的示例代碼,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02
ThreadLocal內(nèi)存泄露的產(chǎn)生原因和處理方法
ThreadLocal 的內(nèi)存泄漏問題通常發(fā)生在使用 ThreadLocal 存儲對象時,尤其是在多線程環(huán)境中,線程池中的線程復(fù)用可能導(dǎo)致一些資源沒有及時清理,從而引發(fā)內(nèi)存泄漏,所以本文給大家介紹了ThreadLocal內(nèi)存泄露的產(chǎn)生原因和處理方法,需要的朋友可以參考下2024-12-12
Mybatis-Plus使用saveOrUpdate及問題解決方法
本文主要介紹了Mybatis-Plus使用saveOrUpdate及問題解決方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01
springBoot整合CXF并實現(xiàn)用戶名密碼校驗的方法
這篇文章主要介紹了springBoot整合CXF并實現(xiàn)用戶名密碼校驗的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08
Java?spring?通過注解方式創(chuàng)建對象的示例詳解
這篇文章主要介紹了java?spring?通過注解方式創(chuàng)建對象,本文結(jié)合示例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-02-02
SpringBoot+Apache tika實現(xiàn)文檔內(nèi)容解析的示例詳解
Apache tika是Apache開源的一個文檔解析工具,本文主要為大家介紹了如何在springboot中引入tika的方式解析文檔,感興趣的小伙伴可以了解一下2023-07-07

