Java虛擬機
- JVM與Java體系結構
- 為什么要學習JVM
- Java與JVM簡介
- Java 語言的核心特性
- JVM:Java 生態的基石
- JVM的架構模型
- 基于棧的指令集架構(Stack-Based)
- 基于寄存器的指令集架構(Register-Based)
- JVM生命周期
- 總結
JVM與Java體系結構
Java不僅僅是一個簡單的編程語言,它是由一系列的軟件與規范組成的技術體系。而JVM是Java程序的運行核心,通過字節碼解釋執行實現"一次編寫,到處運行"特性,而Java體系結構由編程語言規范、類庫體系及JVM運行時環境共同構成,支撐跨平臺面向對象開發。可以說JVM是整個Java平臺的基石,是將Java技術隔絕操作系統與硬件的關鍵部分。
為什么要學習JVM
功利點說,現在找工作的門檻越來越高,JVM知識點經常出現在面試題目中,要想通過面試,我們也需要了解JVM。但是除去面試,程序開發中我們也會遇見:
- 定位 OOM(內存溢出):通過分析堆轉儲(Heap Dump)和 GC 日志,快速定位內存泄漏的根源。
- 線程死鎖:利用 JVM 工具(如 jstack)分析線程狀態,解決多線程并發問題。
- 性能瓶頸:通過 Profiling 工具(如 VisualVM、Arthas)監控 CPU、內存使用情況,找到性能熱點。
這些都需要我們具備JVM的基礎知識,才能在生產中遇到類似問題迎刃而解。掌握了基礎知識,我們可以更好地做到下面幾點:
- 內存管理:理解 JVM 的內存結構(堆、棧、方法區等)能幫助你優化內存分配,避免內存泄漏和頻繁的垃圾回收(GC)。
- 垃圾回收機制:不同場景需要不同的 GC 算法(如 G1、ZGC、Shenandoah),學習 JVM 可以合理選擇并配置 GC,減少程序停頓時間。
- 代碼優化:通過 JIT 編譯器、逃逸分析等機制,理解 JVM 如何優化代碼執行,從而編寫更高效的代碼
Java與JVM簡介
Java 自 1995 年由 Sun Microsystems 發布以來,憑借其 跨平臺能力、面向對象特性 和 豐富的生態系統,迅速成為全球最流行的編程語言之一。而 Java 虛擬機(JVM)作為 Java 技術的核心引擎,通過“一次編寫,到處運行”(Write Once, Run Anywhere)的理念,徹底改變了軟件開發的模式。
下面這張圖是Java技術體系,從這張圖我們可以了解Java與Jvm的關系。應該從這張圖,我們可以形象地認識到JVM是Java語言的核心基石,所以我們要想學習好Java語言,一定要了解JVM。
Java 語言的核心特性
- 跨平臺能力
Java 的跨平臺性依賴于 JVM 的中間層設計。開發者編寫的 .java 文件會被編譯為與平臺無關的 字節碼(.class 文件),由 JVM 在目標平臺上解釋或編譯執行。
示例:同一份 Java 程序可在 Windows、Linux、macOS 上運行,無需修改源碼。
意義:降低多環境適配成本,推動企業級應用的快速部署。
-
面向對象設計(OOP)
Java 是純粹的面向對象語言,其核心思想通過 封裝、繼承、多態 實現:-
封裝:通過 private、protected 關鍵字隱藏實現細節,暴露安全接口。
-
繼承:extends 實現代碼復用,implements 支持多接口擴展。
-
多態:同一方法在不同子類中呈現不同行為(如 Animal 類的 sound() 方法被 Dog 和 Cat 重寫)。
-
-
健壯性與安全性
-
異常處理:強制檢查異常(Checked Exceptions)要求開發者顯式處理潛在錯誤(如 IOException)。
-
內存管理:JVM 自動垃圾回收機制(GC)減少內存泄漏風險。
-
安全沙箱:通過字節碼驗證和安全管理器(SecurityManager)限制惡意代碼訪問系統資源。
-
-
現代語言特性演進
Java 持續吸收現代編程范式的優點:-
Java 8:引入 Lambda 表達式、Stream API,支持函數式編程。
-
Java 11:var 關鍵字簡化局部變量聲明,HTTP Client 支持異步請求。
-
Java 17:密封類(sealed class)限制繼承關系,模式匹配增強代碼可讀性。
-
JVM:Java 生態的基石
- JVM 的核心作用
JVM 是 Java 程序運行的虛擬化環境,主要職責包括:
-
字節碼解釋與執行:將 .class 文件轉換為機器指令。
-
內存管理:分配堆、棧、方法區等內存空間,并自動回收垃圾對象。
-
線程調度:管理多線程的創建、同步與資源競爭。
- JVM 的運行時數據區
JVM 內存劃分為多個核心區域:
-
堆(Heap):存儲對象實例,是垃圾回收的主戰場。
-
方法區(Metaspace):存放類元數據(Java 8 后替代永久代)。
-
虛擬機棧:存儲方法調用的棧幀(局部變量、操作數棧)。
-
程序計數器:記錄當前線程執行的字節碼位置。
-
本地方法棧:支持 Native 方法(如 C/C++ 庫調用)。
- 類加載機制
JVM 通過 雙親委派模型 加載類:
-
加載:從文件、網絡等來源讀取 .class 文件。
-
驗證:確保字節碼符合 JVM 規范,防止惡意代碼注入。
-
準備:為靜態變量分配內存并初始化默認值。
-
解析:將符號引用轉換為直接引用。
-
初始化:執行靜態代碼塊(static{})和賦值操作。
示例:自定義類加載器可實現熱部署(如 Tomcat 為每個 Web 應用單獨加載類)。
- 垃圾回收(GC)機制
JVM 通過 GC 自動回收無用的對象,關鍵算法包括:
-
標記-清除:簡單但易產生內存碎片。
-
復制算法:將存活對象復制到新空間(適用于年輕代)。
-
標記-整理:整理內存碎片(適用于老年代)。
分代收集:根據對象生命周期劃分年輕代(Young Generation)和老年代(Old Generation)。
現代 GC 器:G1(低延遲)、ZGC(TB 級堆內存)、Shenandoah(并發回收)。
調優場景:高并發服務可通過 -XX:+UseG1GC 啟用 G1 垃圾回收器,減少停頓時間。
- JIT 編譯器
JVM 通過 即時編譯(Just-In-Time Compilation) 提升性能:
-
解釋執行:初期逐行解釋字節碼,啟動速度快。
-
熱點代碼優化:頻繁執行的代碼(熱點代碼)被編譯為本地機器碼。
-
逃逸分析:優化對象分配(如棧上分配、鎖消除)。
JVM的架構模型
Java編譯器輸入的指令流是一種基于棧的指令集架構,另外一種常見的指令集架構則是基于寄存器的指令集架構。計算機指令集架構(ISA)是硬件與軟件交互的核心接口,決定了程序如何被編譯和執行。基于棧的指令集架構(如 JVM 字節碼)和 基于寄存器的指令集架構(如 x86、ARM)是兩種經典的設計范式,它們在指令執行方式、性能特點和應用場景上存在顯著差異。
基于棧的指令集架構(Stack-Based)
-
核心原理
數據操作依賴棧結構:所有計算通過操作數棧(Operand Stack)完成。指令從棧頂取操作數:例如,加法指令 iadd 會彈出棧頂兩個整數,相加后結果壓回棧頂。無需顯式指定操作數地址:指令本身不包含寄存器或內存地址,隱含依賴棧頂數據。 -
典型示例(JVM 字節碼)
java// Java 代碼:計算 3 + 5int a = 3;int b = 5;int c = a + b;對應的字節碼:
字節碼
iconst_3 // 將常量3壓入棧頂istore_1 // 彈出棧頂值(3),存入局部變量表第1槽位(a)iconst_5 // 將常量5壓入棧頂istore_2 // 彈出棧頂值(5),存入局部變量表第2槽位(b)iload_1 // 加載局部變量a的值(3)到棧頂iload_2 // 加載局部變量b的值(5)到棧頂iadd // 彈出棧頂兩個值(3和5),相加后結果(8)壓入棧頂istore_3 // 彈出棧頂值(8),存入局部變量表第3槽位(c)
- 優點
-
指令緊湊:無需指定操作數地址,指令長度短(如 JVM 字節碼通常為 1-2 字節)。
-
跨平臺友好:不依賴物理寄存器數量或布局,適合虛擬機實現(如 JVM)。
-
代碼生成簡單:編譯器無需處理寄存器分配,邏輯更簡單。
- 缺點
-
執行速度較慢:頻繁的入棧、出棧操作導致內存訪問開銷大。
-
指令數量多:簡單操作可能需要多條指令(如加載變量到棧頂再計算)。
基于寄存器的指令集架構(Register-Based)
- 核心原理
數據操作依賴寄存器:指令直接讀寫寄存器中的操作數。
顯式指定操作數地址:指令需聲明操作數所在的寄存器或內存地址。
結果直接寫入寄存器:例如,加法指令 ADD R1, R2, R3 表示 R1 = R2 + R3。
2. 典型示例(ARM 匯編)
// 計算 3 + 5,結果存入寄存器 R0
MOV R1, #3 // 將立即數3存入寄存器R1
MOV R2, #5 // 將立即數5存入寄存器R2
ADD R0, R1, R2 // R0 = R1 + R2
- 優點
執行效率高:減少內存訪問次數,數據直接在寄存器中操作。
指令數量少:單條指令可完成復雜操作(如 ADD 直接操作三個寄存器)。
硬件優化潛力大:與現代 CPU 的多級流水線、亂序執行等特性契合。
- 缺點
指令長度較長:需編碼寄存器地址,指令占用空間更大。
依賴硬件寄存器數量:寄存器數量有限的架構(如 x86)可能需頻繁內存交互。
編譯器復雜度高:需優化寄存器分配策略(如避免寄存器溢出)。
JVM生命周期
JVM的生命周期分為三個狀態:啟動、執行和推出
- 啟動: JVM可以通過java命令啟動,接著通過引導類加載器加載類文件,最后找到程序中的main方法,接著開始執行Java應用程序
- 執行: JVM的執行,表示一個已經啟動的JVM開始執行Java程序,執行一個Java程序,真正執行的是一個JVM的進程。
- 退出: JVM的退出有下面幾種情況
- Java程序正常結束,所有的非守護線程結束
- Java程序異常
- 操作系統故障
- 用戶手動關閉JVM
- 調用Runtime或者System的exit方法
總結
我們本章簡單介紹一下Java語言與JVM的結構,之后我們會在專欄文章中,逐步把我們上面介紹的知識點都覆蓋到,爭取能夠做到讓大家能夠對JVM的知識有一個宏觀的認識。