面試必問的Java 運行時架構(gòu)JVM詳解
我剛學(xué) Java 的時候,以為 JVM 就是個"黑盒",代碼寫完往里一扔就能跑。
后來才知道,JVM 里面學(xué)問大了去了。
今天這篇文章,就是我復(fù)習(xí) JVM 時整理的筆記。面試問到 JVM 結(jié)構(gòu),如果你只能說出"堆和棧",那大概率要被追問到懷疑人生。
一、先畫個整體框架
在說每個組件之前,先把 JVM 的結(jié)構(gòu)捋清楚。

記住這個圖,這是整個 JVM 的骨架。
三個核心模塊:
- 類加載器(ClassLoader)—— 把 class 文件加載進來
- 運行時數(shù)據(jù)區(qū)(Runtime Data Area)—— 分配內(nèi)存,存數(shù)據(jù)
- 執(zhí)行引擎(Execution Engine)—— 執(zhí)行字節(jié)碼指令
二、類加載器 ClassLoader
它干什么的?
ClassLoader 負責(zé)把 .class 文件(字節(jié)碼)加載到內(nèi)存中,轉(zhuǎn)換成 JVM 能認識的數(shù)據(jù)結(jié)構(gòu)。
加載流程:
.java 源文件 ? ↓ 編譯 .class 字節(jié)碼文件 ? ↓ ClassLoader 內(nèi)存中的 Class 對象(java.lang.Class)
三個階段
1. 加載(Loading)
- 通過類的全限定名找到 class 文件
- 把字節(jié)流轉(zhuǎn)換成方法區(qū)的數(shù)據(jù)結(jié)構(gòu)
- 在堆里生成一個 Class 對象,作為訪問入口
2. 鏈接(Linking)
- 驗證:檢查字節(jié)碼格式是否正確(安全檢查)
- 準備:給靜態(tài)變量分配內(nèi)存,設(shè)置默認值
- 解析:把符號引用替換成直接引用(比如把"方法名"變成內(nèi)存地址)
3. 初始化(Initialization)
- 執(zhí)行靜態(tài)代碼塊和靜態(tài)變量的賦值
- 這是真正執(zhí)行代碼的地方
雙親委派模型
這塊是面試高頻追問。
三層類加載器:
| 加載器 | 負責(zé)加載 | 例子 |
|---|---|---|
| Bootstrap ClassLoader | JDK 核心類 | String, Object |
| Extension ClassLoader | JDK 擴展類 | javax.* |
| Application ClassLoader | 用戶 classpath | 自己寫的類 |
雙親委派流程:
加載請求 ? ↓ ApplicationClassLoader ? ↓ 問爸 ExtensionClassLoader ? ↓ 問爸 BootstrapClassLoader ? ↓ 找不到 ExtensionClassLoader 嘗試加載 ? ↓ 找不到 ApplicationClassLoader 嘗試加載 ? ↓ 成功 返回 Class
為什么這樣設(shè)計?
我當(dāng)初也被問懵了,后來想明白了一個例子:
如果你自己寫一個
java.lang.String,沒有雙親委派的話,JVM 就會加載你自己寫的 String,安全性全沒了。
雙親委派保證:核心類庫優(yōu)先被 Bootstrap 加載,防止用戶代碼覆蓋 JDK。
三、運行時數(shù)據(jù)區(qū) Runtime Data Area
這是 JVM 里最重要的部分,也是面試追問的重災(zāi)區(qū)。
1. 程序計數(shù)器(Program Counter Register)
特點:
- 很小,只存一個指針
- 指向下一條要執(zhí)行的字節(jié)碼指令
- 線程私有:每個線程都有自己的 PC
為什么不會 OOM?
因為它只存一個指令地址,大小固定,不會動態(tài)增長。
唯一沒有 GC 的區(qū)域。
2. 虛擬機棧(VM Stack)
特點:
- 線程私有
- 存儲方法調(diào)用的數(shù)據(jù)
- 每個方法叫一個棧幀(Stack Frame)
一個棧幀包含:
- 局部變量表(方法參數(shù) + 局部變量)
- 操作數(shù)棧(計算時的臨時空間)
- 動態(tài)鏈接(指向常量池的引用)
- 返回地址
會 OOM 嗎?
會。如果遞歸沒寫好,棧深度超過限制,就會 StackOverflowError。
我之前寫遞歸算法就踩過這個坑:
// 死遞歸,棧溢出
public int sum(int n) {
return sum(n) + 1; // 沒有終止條件
}3. 本地方法棧(Native Method Stack)
和虛擬機棧一樣,只不過服務(wù)的是本地方法(native keyword 修飾的方法)。
JNI 調(diào)用(C/C++ 寫的代碼)會用到這里。
4. 堆(Heap)
JVM 里最大的內(nèi)存區(qū)域。
- 存儲對象實例和數(shù)組
- 線程共享:所有線程都能訪問
- GC 的主要戰(zhàn)場
為什么要分代?
因為對象的生命周期不同:
- 新生代:剛創(chuàng)建的對象(朝生夕死)
- 老年代:存活時間長的對象
- 分代后,GC 更有針對性,效率更高
5. 方法區(qū)(Method Area)
存儲:
- 類的元信息(類名、修飾符、父類)
- 類的結(jié)構(gòu)(字段、方法)
- 運行時常量池(字面量和符號引用)
- JIT 編譯后的代碼緩存
JDK 1.8 的變化:
之前方法區(qū)在堆里,叫 Permanent Generation(永久代)。
JDK 1.8 改成了 Metaspace,放在本地內(nèi)存里。
| 版本 | 方法區(qū)實現(xiàn) | 位置 |
|---|---|---|
| JDK 1.7 | PermGen | 堆內(nèi) |
| JDK 1.8 | Metaspace | 本地內(nèi)存 |
為什么要改?
因為 PermGen 容易 OOM。你要是項目中依賴了一堆 jar 包,PermGen 分分鐘爆掉。
四、執(zhí)行引擎 Execution Engine
它干什么的?
把字節(jié)碼翻譯成機器碼,讓 CPU 執(zhí)行。
解釋器 vs JIT 編譯器
| 組件 | 特點 | 適用場景 |
|---|---|---|
| 解釋器 | 逐行翻譯,立即執(zhí)行 | 啟動階段,一次性執(zhí)行 |
| JIT 編譯器 | 編譯成機器碼,緩存起來 | 熱點代碼,多次執(zhí)行 |
JIT 的熱點探測:
JVM 會監(jiān)控方法的執(zhí)行頻率,熱點代碼會被 JIT 編譯成機器碼。
兩個計數(shù)器:
- 方法調(diào)用計數(shù)器
- 回邊計數(shù)器(循環(huán)體)
超過閾值就觸發(fā)編譯。
垃圾回收器
GC 是 JVM 里最復(fù)雜也最常問的部分。
常見 GC 算法:
| 算法 | 原理 | 缺點 |
|---|---|---|
| 標記-清除 | 標記存活對象,清理未標記的 | 產(chǎn)生內(nèi)存碎片 |
| 復(fù)制 | 把活對象復(fù)制到另一半空間 | 浪費一半內(nèi)存 |
| 標記-整理 | 標記后整理內(nèi)存 | 整理耗時 |
常見垃圾回收器:
- Serial(單線程)
- Parallel(多線程,吞吐量優(yōu)先)
- CMS(并發(fā)標記清除,低停頓)
- G1(REGION劃分,可預(yù)測停頓)
- ZGC / Shenandoah(超低停頓,JDK 11+)
五、本地接口 JNI
Java 不是萬能的,有些場景需要調(diào)用本地代碼(C/C++)。
JNI 就是 Java 和 native 代碼之間的橋梁。
典型場景:
- 調(diào)用操作系統(tǒng) API
- 追求極致性能(數(shù)學(xué)計算)
- 訪問硬件
public native String hello(); // native 方法,調(diào)用 C 實現(xiàn)
六、記憶口訣
JVM 三大部分: ┌─────────────────────────────────┐ │ 類加載器 ──── 加載字節(jié)碼 ? ? ? │ │ 運行時數(shù)據(jù)區(qū) ── 分配內(nèi)存 ? ? ? │ │ 執(zhí)行引擎 ──── 執(zhí)行字節(jié)碼 ? ? ? │ └─────────────────────────────────┘ 運行時數(shù)據(jù)區(qū)五塊: 計數(shù)器(無GC) 虛擬機棧(存方法調(diào)用,會OOM) 本地方法棧(native方法) 堆(對象,會GC) 方法區(qū)(類信息) 雙親委派: Bootstrap → Extension → Application
寫在最后
JVM 這塊知識,面試基本必問。
我踩過的坑是:只背概念,不畫圖。
后來我把 JVM 的結(jié)構(gòu)畫了 5 遍,把每個區(qū)域的作用、特點、常見問題都寫上,面試時直接畫給面試官看。
面試官當(dāng)時眼睛一亮,說:"這個理解方式不錯。"
技術(shù)表達很重要。能畫出來、講清楚,比背出來強 100 倍。
到此這篇關(guān)于JVM 有哪些組成部分:面試必問的 Java 運行時架構(gòu)的文章就介紹到這了,更多相關(guān)java運行時架構(gòu)jvm內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java安全 ysoserial CommonsCollections3示例分析
這篇文章主要為大家介紹了Java安全 ysoserial CommonsCollections3示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10
Java通過Caffeine和自定義注解實現(xiàn)本地防抖接口限流
Caffeine?是目前Java領(lǐng)域最熱門,性能最高的本地內(nèi)存緩存庫,本文為大家詳細介紹了如何利用Caffeine和自定義注解實現(xiàn)本地防抖接口限流功能,需要的可以了解下2025-05-05
MyBatis處理懶加載和預(yù)加載的實戰(zhàn)詳解
在Java開發(fā)中,MyBatis是一個廣泛使用的持久層框架,它為開發(fā)者提供了靈活、強大的數(shù)據(jù)訪問能力,兩種常用的加載策略是懶加載和預(yù)加載,本文將介紹這兩種加載方式的概念、適用場景以及MyBatis中的實現(xiàn)方式,需要的朋友可以參考下2026-02-02
深入理解java動態(tài)代理的兩種實現(xiàn)方式(JDK/Cglib)
本篇文章主要介紹了java動態(tài)代理的兩種實現(xiàn)方式,詳細的介紹了JDK和Cglib的實現(xiàn)方法,具有一定的參考價值,有興趣的可以了解一下2017-04-04
java字符串日期類Date和Calendar相互轉(zhuǎn)化及相關(guān)常用方法
Java語言的Calendar(日歷),Date(日期),和DateFormat(日期格式)組成了Java標準的一個基本但是非常重要的部分,下面這篇文章主要給大家介紹了關(guān)于java字符串日期類Date和Calendar相互轉(zhuǎn)化及相關(guān)常用方法的相關(guān)資料,需要的朋友可以參考下2023-12-12
解讀@ResponseBody與@RequestBody注解的用法
這篇文章主要介紹了Spring MVC中的@ResponseBody和@RequestBody注解的用法,@ResponseBody注解用于將Controller方法的返回對象轉(zhuǎn)換為指定格式(如JSON)并通過Response響應(yīng)給客戶端,@RequestBody注解用于讀取HTTP請求的內(nèi)容2024-11-11
IDEA中實體類(POJO)與JSON快速互轉(zhuǎn)問題
這篇文章主要介紹了IDEA中實體類(POJO)與JSON快速互轉(zhuǎn),本文通過圖文實例代碼相結(jié)合給大家介紹的非常詳細,需要的朋友可以參考下2022-08-08

