JVM與JMM深度解析:從Java 8到Java 21的演進

文章目錄

    • 第一部分:JVM基礎概念與架構
      • JVM是什么?
      • JVM整體架構
      • 運行時數據區
      • 類加載機制
      • 執行引擎
    • 第二部分:Java內存模型(JMM)
      • 什么是Java內存模型
      • JMM的核心問題
      • 主內存與工作內存
      • 內存間交互操作
      • 重排序與happens-before原則
      • volatile關鍵字
      • synchronized關鍵字
    • 第三部分:Java 8到Java 21的JVM和JMM演進
      • Java 8到Java 11的變化
        • JVM架構變化
        • JMM相關變化
      • Java 11到Java 17的變化
        • JVM架構變化
        • JMM相關變化
      • Java 17到Java 21的變化
        • JVM架構變化
        • JMM相關結構化并發
    • 第四部分:垃圾回收器詳解
      • G1垃圾回收器
        • 核心特性
        • G1的工作流程
        • 適用場景
        • 調優參數
      • ZGC垃圾回收器
        • 核心特性
        • ZGC的工作流程
        • 適用場景
        • 調優參數
      • Shenandoah垃圾回收器
        • 核心特性
        • Shenandoah的工作流程
        • 適用場景
        • 調優參數
      • 垃圾回收器性能對比
        • 停頓時間對比
        • 吞吐量對比
        • 內存開銷對比
    • 第五部分:虛擬線程與結構化并發
      • 虛擬線程簡介
        • 虛擬線程與平臺線程的區別
        • 虛擬線程的使用場景
        • 虛擬線程的使用示例
      • 結構化并發API
        • 核心概念
        • 結構化并發的優勢
        • 結構化并發的使用示例
    • 第六部分:面試常見問題與回答
      • Q1: G1、ZGC和Shenandoah的主要區別是什么?
      • Q2: 為什么ZGC能實現低于10ms的停頓時間?
      • Q3: G1收集器的"可預測停頓"是如何實現的?
      • Q4: Java 17相比Java 8在GC方面有哪些重要改進?
      • Q5: 什么是雙親委派模型?如何破壞雙親委派模型?
      • Q6: 什么是JIT編譯器?它如何提高Java程序的性能?
      • Q7: Java內存模型中的happens-before原則是什么?
      • Q8: 虛擬線程與傳統線程的區別是什么?
      • Q9: 元空間與永久代的區別是什么?
      • Q10: 如何選擇合適的垃圾回收器?
    • 總結
    • 參考資料

在Java開發者的職業生涯中,理解JVM(Java虛擬機)和JMM(Java內存模型)是進階的必經之路。無論是日常開發還是面試,這都是繞不開的話題。本文將從實用角度出發,帶你深入了解JVM和JMM的核心概念,并對比Java 8、Java 11、Java 17和Java 21各版本的實現差異,幫助大家更好的了解JVM和JMM

第一部分:JVM基礎概念與架構

JVM是什么?

簡單來說,JVM是一個抽象的計算機,它提供了一個平臺無關的執行環境,讓Java程序可以"一次編寫,到處運行"。當我們編寫Java代碼時,編譯器會將源代碼(.java文件)編譯成字節碼(.class文件),而JVM則負責解釋執行這些字節碼。

這種設計帶來了兩個關鍵優勢:

  • 平臺獨立性:Java程序可以在任何安裝了JVM的設備上運行
  • 安全性:JVM提供了一個受控的執行環境,限制了程序對系統資源的直接訪問

JVM整體架構

JVM的架構可以分為三大部分:

  1. 類加載子系統:負責加載、鏈接和初始化類
  2. 運行時數據區:JVM內存模型,包括方法區、堆、Java棧、本地方法棧和程序計數器
  3. 執行引擎:包括即時編譯器(JIT)、解釋器和垃圾回收器

運行時數據區

JVM的內存區域劃分是面試的高頻考點,包括:

  1. 程序計數器

    • 當前線程執行的字節碼指令地址
    • 線程私有,是唯一不會發生OOM的內存區域
  2. Java虛擬機棧

    • 線程私有,生命周期與線程相同
    • 每個方法執行時會創建一個棧幀,包含局部變量表、操作數棧、動態鏈接和方法出口等信息
    • 可能拋出StackOverflowError和OutOfMemoryError
  3. 本地方法棧

    • 為本地(Native)方法服務
    • 也可能拋出StackOverflowError和OutOfMemoryError
  4. Java堆

    • 線程共享,存儲對象實例
    • 垃圾收集器管理的主要區域,也稱為"GC堆"
    • 可能拋出OutOfMemoryError
  5. 方法區

    • 存儲已被虛擬機加載的類信息、常量、靜態變量等
    • 在HotSpot虛擬機中,JDK8前稱為"永久代",JDK8后稱為"元空間"
    • 可能拋出OutOfMemoryError

類加載機制

類加載過程分為三個階段:

  1. 加載:查找并加載類的二進制數據
  2. 鏈接
    • 驗證:確保加載的類符合JVM規范
    • 準備:為類的靜態變量分配內存并設置初始值
    • 解析:將符號引用轉換為直接引用
  3. 初始化:執行類構造器<clinit>()方法

類加載器遵循三個重要原則:

  • 委托機制:先讓父加載器嘗試加載
  • 可見性:子加載器可以看到父加載器加載的類,反之不行
  • 唯一性:同一個類(由類名和加載器共同決定)只會被加載一次

JVM提供了三種內置的類加載器:

  1. 啟動類加載器(Bootstrap ClassLoader)

    • 負責加載Java核心類庫
    • 由C++實現,是JVM的一部分
  2. 擴展類加載器(Extension ClassLoader)

    • 負責加載Java擴展類庫
    • JDK 9后改名為平臺類加載器(Platform ClassLoader)
  3. 應用類加載器(Application ClassLoader)

    • 負責加載應用程序classpath下的類

執行引擎

執行引擎是JVM的核心組件,負責執行字節碼指令:

  1. 解釋器:逐條解釋執行字節碼指令
  2. JIT編譯器:將熱點代碼編譯成本地機器碼,提高執行效率
  3. 垃圾回收器:自動回收不再使用的內存

JVM采用混合模式執行:

  • 解釋執行:啟動快,執行慢
  • 編譯執行:啟動慢,執行快

通過熱點代碼探測,JVM能夠在解釋執行和編譯執行之間取得平衡,實現"解釋+編譯"的混合執行模式。

第二部分:Java內存模型(JMM)

什么是Java內存模型

Java內存模型(Java Memory Model,簡稱JMM)是Java虛擬機規范中定義的一種內存模型,用于屏蔽各種硬件和操作系統的內存訪問差異,讓Java程序在各種平臺下都能達到一致的內存訪問效果。

JMM不是真實存在的物理內存區域,而是一種抽象概念,是一組規范,定義了程序中各個變量的訪問方式。

JMM的核心問題

JMM主要解決了三個核心問題:

  1. 可見性:一個線程對共享變量的修改,能夠及時被其他線程看到
  2. 原子性:一個操作或多個操作要么全部執行并且不會被中斷,要么都不執行
  3. 有序性:程序執行的順序按照代碼的先后順序執行

主內存與工作內存

在JMM中,所有的變量都存儲在主內存(Main Memory)中,每個線程還有自己的工作內存(Working Memory):

  • 主內存:所有線程共享的內存區域,存儲所有變量的主副本
  • 工作內存:每個線程私有的內存區域,存儲該線程使用到的變量的副本

線程對變量的所有操作(讀取、賦值等)都必須在工作內存中進行,而不能直接讀寫主內存中的變量。不同線程之間也無法直接訪問對方工作內存中的變量,線程間變量值的傳遞均需要通過主內存來完成。

內存間交互操作

JMM定義了8種操作來完成主內存和工作內存的交互:

  1. lock(鎖定):作用于主內存的變量,把變量標識為一條線程獨占狀態
  2. unlock(解鎖):作用于主內存的變量,釋放變量的鎖定狀態
  3. read(讀取):作用于主內存的變量,把變量的值從主內存傳輸到線程的工作內存
  4. load(載入):作用于工作內存的變量,把read操作從主內存中得到的變量值放入工作內存的變量副本中
  5. use(使用):作用于工作內存的變量,把工作內存中的變量值傳遞給執行引擎
  6. assign(賦值):作用于工作內存的變量,把執行引擎接收到的值賦給工作內存的變量
  7. store(存儲):作用于工作內存的變量,把工作內存中的變量值傳送到主內存中
  8. write(寫入):作用于主內存的變量,把store操作從工作內存中得到的變量值放入主內存的變量中

重排序與happens-before原則

為了提高性能,編譯器和處理器常常會對指令進行重排序,分為三種類型:

  1. 編譯器優化重排序:編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執行順序
  2. 指令級并行重排序:現代處理器采用了指令級并行技術來將多條指令重疊執行
  3. 內存系統重排序:由于處理器使用緩存和讀/寫緩沖區,使得加載和存儲操作看上去可能是在亂序執行

這些重排序可能導致多線程程序出現內存可見性問題。為此,JMM定義了happens-before原則,用于描述操作之間的內存可見性。如果一個操作happens-before另一個操作,那么第一個操作的結果對第二個操作可見。

主要的happens-before規則包括:

  1. 程序順序規則:一個線程中的每個操作,happens-before于該線程中的任意后續操作
  2. 監視器鎖規則:對一個鎖的解鎖,happens-before于隨后對這個鎖的加鎖
  3. volatile變量規則:對一個volatile變量的寫,happens-before于任意后續對這個volatile變量的讀
  4. 傳遞性:如果A happens-before B,且B happens-before C,那么A happens-before C
  5. 線程啟動規則:Thread對象的start()方法happens-before于該線程的任何動作
  6. 線程終止規則:線程中的所有操作都happens-before于其他線程檢測到該線程已經終止

volatile關鍵字

volatile是Java提供的最輕量級的同步機制,它保證了:

  1. 可見性:對一個volatile變量的寫,對其他線程立即可見
  2. 有序性:禁止指令重排序優化

volatile的內存語義:

  • 當寫一個volatile變量時,JMM會把該線程對應的本地內存中的共享變量值刷新到主內存
  • 當讀一個volatile變量時,JMM會把該線程對應的本地內存置為無效,從主內存中讀取共享變量

volatile適用于一寫多讀的場景,但不保證原子性。例如,volatile++操作不是原子的,因為它包含讀取、計算、寫入三個步驟。

synchronized關鍵字

synchronized是Java中的重量級鎖,它可以保證被修飾的代碼塊或方法在同一時刻只能被一個線程執行,從而保證了:

  1. 原子性:確保多個操作作為一個整體不可分割
  2. 可見性:synchronized的可見性是由"對一個變量執行unlock操作之前,必須先把此變量同步回主內存中(執行store、write操作)"這條規則獲得的
  3. 有序性:synchronized保證了一個變量在同一個時刻只能由一個線程對其進行lock操作

synchronized的內存語義:

  • 進入synchronized塊時,會清空工作內存中的共享變量值,從主內存中重新讀取
  • 退出synchronized塊時,會將工作內存中的共享變量值刷新到主內存中

第三部分:Java 8到Java 21的JVM和JMM演進

Java 8到Java 11的變化

JVM架構變化
  1. 永久代到元空間的轉變

    • Java 8 將永久代(PermGen)完全移除,取而代之的是元空間(Metaspace)
    • 元空間使用本地內存而非JVM堆內存,默認情況下可以動態增長,減少了"java.lang.OutOfMemoryError: PermGen space"錯誤
    • 類的元數據信息放到本地內存,常量池和靜態變量放到Java堆中
  2. 默認垃圾收集器變更

    • Java 8 默認使用Parallel垃圾收集器
    • Java 11 默認使用G1垃圾收集器,提供更好的延遲可預測性和更高的吞吐量
  3. G1收集器的改進

    • Java 11 中G1收集器實現了并行Full GC,大幅提高了Full GC的性能
    • 引入了基于NUMA感知的內存分配優化
  4. 統一JVM日志系統

    • Java 9 引入,Java 11 完善了統一的JVM日志系統
    • 提供了一致的命令行選項和日志輸出格式,便于問題診斷
JMM相關變化
  1. 變量句柄(VarHandle)

    • Java 9 引入,Java 11 完善
    • 提供比反射更精細的內存訪問控制,支持原子操作和內存屏障
    • 可以替代Unsafe類的部分功能,提供類型安全的方式訪問對象字段和數組元素
  2. 內存管理優化

    • Java 10 開始,JVM能夠識別容器的內存限制(如Docker容器)
    • 改進了內存分配策略,更好地適應現代云環境

Java 11到Java 17的變化

JVM架構變化
  1. ZGC的引入與改進

    • Java 11 引入ZGC作為實驗性功能
    • Java 15 將ZGC從實驗特性轉為產品特性
    • Java 17 進一步優化ZGC,提供更低的延遲和更高的吞吐量
    • ZGC支持并發類卸載、并發堆收縮,大幅減少停頓時間
  2. Shenandoah GC

    • Java 12 引入Shenandoah GC作為實驗性功能
    • Java 15 將Shenandoah GC從實驗特性轉為產品特性
    • 專注于低停頓時間,適用于對響應時間敏感的應用
  3. CMS收集器的移除

    • Java 14 正式移除了CMS(Concurrent Mark Sweep)垃圾收集器
  4. 彈性元空間

    • Java 16 引入彈性元空間能力
    • 更高效地將未使用的HotSpot類元數據(即元空間)內存返回給操作系統
    • 減少了元空間內存占用,提高了內存利用率
JMM相關變化
  1. 增強的同步機制

    • Java 15 引入了改進的偏向鎖實現
    • Java 16 開始棄用偏向鎖,并在Java 18中完全移除
  2. 內存管理優化

    • 改進了G1和ZGC的內存回收策略
    • 優化了對大對象和長壽命對象的處理

Java 17到Java 21的變化

JVM架構變化
  1. 虛擬線程的引入

    • Java 19 引入虛擬線程作為預覽特性
    • Java 21 正式發布虛擬線程
    • 虛擬線程是輕量級線程實現,由JVM而不是操作系統管理
    • 支持高吞吐量的并發應用,可以創建數百萬個虛擬線程
  2. GC改進

    • 進一步優化ZGC和G1收集器
    • 改進了垃圾收集器的并行和并發能力
    • 降低了Full GC的頻率和停頓時間
  3. JIT編譯器優化

    • 改進了即時編譯器的優化策略
    • 增強了逃逸分析和內聯優化
    • 提高了代碼執行效率
JMM相關結構化并發
  1. 結構化并發API

    • Java 19 引入結構化并發API作為預覽特性
    • Java 21 繼續改進結構化并發API
    • 提供了更簡單、更可靠的并發編程模型
    • 更好地支持異步編程和錯誤處理
  2. 外部內存訪問API

    • Java 14 引入外部內存訪問API作為孵化器特性
    • Java 21 將其作為正式特性發布
    • 允許Java程序安全高效地訪問Java堆外的內存

第四部分:垃圾回收器詳解

G1垃圾回收器

G1(Garbage-First)是一種面向服務端應用的垃圾回收器,目標是替代CMS。

核心特性
  1. 分區(Region)設計:將堆內存劃分為多個大小相等的區域,每個區域可以是Eden、Survivor、Old或Humongous區域
  2. 可預測的停頓時間:通過-XX:MaxGCPauseMillis參數設置目標停頓時間
  3. 混合收集:既有年輕代收集,也有老年代收集
  4. 增量收集:不需要一次性收集整個堆,而是選擇部分區域進行收集
G1的工作流程
  1. 初始標記(Initial Marking):標記GC Roots能直接關聯到的對象,需要STW(Stop The World)
  2. 并發標記(Concurrent Marking):對堆中對象進行可達性分析,與應用線程并發執行
  3. 最終標記(Final Marking):處理并發標記階段遺留的少量SATB(Snapshot At The Beginning)記錄,需要STW
  4. 篩選回收(Live Data Counting and Evacuation):對各個Region的回收價值和成本進行排序,根據用戶期望的停頓時間制定回收計劃,需要STW
適用場景
  1. 需要較低GC延遲的大型應用(堆內存4GB-32GB)
  2. 電商、金融等對響應時間敏感的在線業務系統
  3. 多核CPU環境
調優參數
-XX:+UseG1GC                   // 使用G1垃圾收集器
-XX:MaxGCPauseMillis=200       // 設置目標最大停頓時間為200毫秒
-XX:InitiatingHeapOccupancyPercent=45  // 設置觸發標記周期的堆占用率閾值
-XX:G1NewSizePercent=5         // 設置年輕代最小占堆的百分比
-XX:G1MaxNewSizePercent=60     // 設置年輕代最大占堆的百分比

ZGC垃圾回收器

ZGC(Z Garbage Collector)是一款低延遲垃圾收集器,目標是在任何堆大小下都能將停頓時間控制在10ms以內。

核心特性
  1. 并發處理:幾乎所有GC操作都與應用線程并發執行,包括標記、轉移和重定位
  2. 基于區域的內存管理:類似G1,但區域大小動態可變(2MB-4TB)
  3. 標記-復制算法:使用復制算法進行內存整理
  4. 顏色指針:利用64位指針中的一些位作為標記位,實現快速對象定位和并發轉移
  5. 支持超大堆:最高支持16TB的堆內存
ZGC的工作流程
  1. 并發標記:標記存活對象,與應用線程并發執行
  2. 并發轉移準備:選擇要清理的區域,與應用線程并發執行
  3. 并發轉移:將存活對象從選定區域復制到新區域,與應用線程并發執行
  4. 并發重映射:更新引用,指向對象的新位置,與應用線程并發執行
適用場景
  1. 對延遲極為敏感的應用(如金融交易、游戲服務器)
  2. 大內存應用(堆內存>32GB)
  3. 需要穩定響應時間的實時系統
調優參數
-XX:+UseZGC                    // 使用ZGC垃圾收集器
-XX:ZAllocationSpikeTolerance=2 // 設置內存分配峰值容忍度
-XX:+UnlockExperimentalVMOptions // 解鎖實驗性VM選項(Java 15之前需要)
-XX:ConcGCThreads=2            // 設置并發GC線程數

Shenandoah垃圾回收器

Shenandoah是一款低延遲垃圾收集器,與ZGC類似,但實現方式不同。

核心特性
  1. 并發整理:與ZGC類似,支持并發標記和并發整理
  2. Brooks轉發指針:每個對象都有一個額外的引用字段,指向對象的當前位置
  3. 不分代:默認不使用分代收集
  4. 連接矩陣:替代G1的記憶集,降低內存開銷
Shenandoah的工作流程
  1. 初始標記:標記GC Roots直接引用的對象,需要STW
  2. 并發標記:遞歸標記整個對象圖,與應用線程并發執行
  3. 最終標記:處理剩余的SATB緩沖區,需要STW
  4. 并發清理:回收確定為垃圾的區域,與應用線程并發執行
  5. 并發整理:將存活對象復制到新的內存區域,與應用線程并發執行
適用場景
  1. 需要低延遲但內存不是特別大的應用(堆內存8GB-32GB)
  2. 對延遲敏感的Web應用和服務
  3. 混合工作負載的應用
調優參數
-XX:+UseShenandoahGC           // 使用Shenandoah垃圾收集器
-XX:ShenandoahGCHeuristics=adaptive // 設置啟發式策略
-XX:+UnlockExperimentalVMOptions // 解鎖實驗性VM選項(Java 15之前需要)
-XX:ConcGCThreads=2            // 設置并發GC線程數

垃圾回收器性能對比

停頓時間對比
  1. Parallel GC:百毫秒級,與堆大小成正比
  2. G1 GC:目標通常為100-200毫秒,但可能出現偶發的長時間停頓
  3. Shenandoah:通常在10-100毫秒之間,與堆大小關系較小
  4. ZGC:通常小于10毫秒,幾乎與堆大小無關
吞吐量對比
  1. Parallel GC:最高(約99%)
  2. G1 GC:中高(約95-98%)
  3. Shenandoah:中等(約90-95%)
  4. ZGC:中等(約90-95%),但在Java 17后有顯著提升
內存開銷對比
  1. Parallel GC:最低
  2. G1 GC:中等(記憶集占用額外內存)
  3. Shenandoah:中高(Brooks轉發指針占用額外內存)
  4. ZGC:中高(顏色指針機制占用額外內存)

更詳細的介紹可以參考另一篇博文

第五部分:虛擬線程與結構化并發

虛擬線程簡介

虛擬線程是Java 21引入的重要特性,它是一種輕量級線程實現,由JVM而不是操作系統管理。虛擬線程的設計目標是提高應用程序的吞吐量,特別是在處理大量并發任務時。

虛擬線程與平臺線程的區別
  1. 創建成本:虛擬線程的創建成本遠低于平臺線程
  2. 內存占用:虛擬線程的內存占用很小,可以創建數百萬個
  3. 調度方式:虛擬線程由JVM調度,而平臺線程由操作系統調度
  4. 阻塞行為:虛擬線程阻塞時不會阻塞底層的平臺線程
虛擬線程的使用場景
  1. 高并發服務器:處理大量并發連接
  2. 微服務架構:每個請求使用一個虛擬線程處理
  3. 異步編程:簡化異步編程模型,使代碼更易于理解和維護
虛擬線程的使用示例
// 創建并啟動一個虛擬線程
Thread vThread = Thread.startVirtualThread(() -> {System.out.println("Running in a virtual thread");
});// 使用虛擬線程執行器
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {IntStream.range(0, 10_000).forEach(i -> {executor.submit(() -> {// 任務代碼return i;});});
}

結構化并發API

結構化并發API是Java 19引入的預覽特性,在Java 21中繼續改進。它提供了一種更簡單、更可靠的并發編程模型,特別適合與虛擬線程配合使用。

核心概念
  1. 結構化任務作用域:定義任務的生命周期和邊界
  2. 子任務:在作用域內創建的任務
  3. 作用域關閉:確保所有子任務在作用域關閉前完成
結構化并發的優勢
  1. 簡化錯誤處理:子任務的異常會傳播到父作用域
  2. 自動取消:當一個子任務失敗時,其他子任務會被自動取消
  3. 避免資源泄漏:確保所有子任務在作用域關閉前完成或取消
  4. 提高代碼可讀性:使并發代碼的結構更加清晰
結構化并發的使用示例
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {Future<String> user = scope.fork(() -> findUser(userId));Future<Integer> order = scope.fork(() -> fetchOrder(orderId));scope.join();           // 等待所有任務完成scope.throwIfFailed();  // 如果有任務失敗,拋出異常// 使用結果processUserOrder(user.resultNow(), order.resultNow());
}

第六部分:面試常見問題與回答

Q1: G1、ZGC和Shenandoah的主要區別是什么?

回答

  • G1采用分區設計,兼顧吞吐量和停頓時間,適合中大型堆內存應用
  • ZGC專注于極低延遲,使用顏色指針技術,適合大內存和對延遲極為敏感的應用
  • Shenandoah也專注于低延遲,使用Brooks轉發指針,適合中等內存和混合工作負載

Q2: 為什么ZGC能實現低于10ms的停頓時間?

回答:ZGC通過顏色指針技術和并發處理實現極低停頓。顏色指針利用64位指針中的一些位作為標記位,使得對象定位和轉移能夠并發執行,幾乎所有GC操作(包括標記、轉移和重定位)都與應用線程并發進行,只有極少數操作需要STW。

Q3: G1收集器的"可預測停頓"是如何實現的?

回答:G1通過以下機制實現可預測停頓:

  1. 將堆劃分為多個大小相等的區域(Region)
  2. 維護每個區域的垃圾密度信息
  3. 根據用戶設置的目標停頓時間(-XX:MaxGCPauseMillis)
  4. 在回收時優先選擇回收價值最高(垃圾最多)的區域
  5. 動態調整年輕代大小和回收區域數量,以接近目標停頓時間

Q4: Java 17相比Java 8在GC方面有哪些重要改進?

回答

  1. 默認GC從Parallel變為G1,更注重延遲而非單純吞吐量
  2. 引入并完善了ZGC和Shenandoah兩款低延遲收集器
  3. 移除了老舊的CMS收集器
  4. G1收集器得到多項優化,包括并行Full GC、NUMA感知等
  5. 引入彈性元空間,優化了元空間的內存管理
  6. 改進了GC的日志和監控能力

Q5: 什么是雙親委派模型?如何破壞雙親委派模型?

回答:雙親委派模型是指當一個類加載器收到類加載請求時,它首先不會自己嘗試加載這個類,而是把這個請求委派給父類加載器去完成。只有當父加載器無法完成加載請求時,子加載器才會嘗試自己加載。

破壞雙親委派模型的方式:

  1. 重寫loadClass()方法:直接覆蓋loadClass()方法,不再調用父類加載器
  2. 使用線程上下文類加載器:通過Thread.currentThread().getContextClassLoader()獲取上下文類加載器,打破了類加載器的層次結構
  3. 使用OSGi等模塊化系統:OSGi實現了自己的類加載器架構,允許同一個類在不同的模塊中加載

Q6: 什么是JIT編譯器?它如何提高Java程序的性能?

回答:JIT(Just-In-Time)編譯器是JVM的一個組件,它在運行時將熱點代碼(頻繁執行的代碼)編譯成本地機器碼,從而提高執行效率。

JIT編譯器提高性能的方式:

  1. 熱點代碼識別:通過計數器或采樣識別頻繁執行的代碼
  2. 即時編譯:將字節碼編譯成優化的本地機器碼
  3. 內聯優化:將方法調用直接內聯到調用點,減少方法調用開銷
  4. 逃逸分析:分析對象的作用域,優化內存分配和同步
  5. 循環優化:提取循環不變量,減少計算量
  6. 分層編譯:結合解釋執行和不同級別的編譯優化,平衡啟動速度和峰值性能

Q7: Java內存模型中的happens-before原則是什么?

回答:happens-before是Java內存模型中的一個核心概念,用于描述操作之間的內存可見性。如果操作A happens-before操作B,那么A的結果對B可見。

主要的happens-before規則包括:

  1. 程序順序規則:一個線程中的每個操作,happens-before于該線程中的任意后續操作
  2. 監視器鎖規則:對一個鎖的解鎖,happens-before于隨后對這個鎖的加鎖
  3. volatile變量規則:對一個volatile變量的寫,happens-before于任意后續對這個volatile變量的讀
  4. 線程啟動規則:Thread對象的start()方法happens-before于該線程的任何動作
  5. 線程終止規則:線程中的所有操作都happens-before于其他線程檢測到該線程已經終止
  6. 傳遞性:如果A happens-before B,且B happens-before C,那么A happens-before C

Q8: 虛擬線程與傳統線程的區別是什么?

回答

  1. 實現方式:虛擬線程由JVM實現和管理,傳統線程(平臺線程)由操作系統實現和管理
  2. 資源消耗:虛擬線程非常輕量,可以創建數百萬個;平臺線程較重,通常只能創建數千個
  3. 調度方式:虛擬線程使用協作式調度,平臺線程使用搶占式調度
  4. 阻塞行為:虛擬線程阻塞時會讓出底層的載體線程,而不會阻塞操作系統線程
  5. 棧大小:虛擬線程的棧可以動態增長,平臺線程的棧大小是固定的
  6. 適用場景:虛擬線程適合IO密集型任務,平臺線程適合CPU密集型任務

Q9: 元空間與永久代的區別是什么?

回答

  1. 存儲位置:永久代位于JVM堆內存中,而元空間使用本地內存(Native Memory)
  2. 內存限制:永久代有固定大小限制,元空間默認可以動態增長,只受系統內存限制
  3. 垃圾回收:元空間的垃圾回收效率更高,主要針對類元數據的回收
  4. OOM風險:永久代容易發生"PermGen space"的OOM錯誤,元空間降低了這種風險
  5. 內容存儲:永久代存儲類元數據、常量池和靜態變量,元空間只存儲類元數據,常量池和靜態變量移到了堆中

Q10: 如何選擇合適的垃圾回收器?

回答:選擇垃圾回收器應考慮以下因素:

  1. 應用特性:

    • 批處理、離線計算類應用:選擇Parallel GC,獲得最高吞吐量
    • 一般在線業務系統:選擇G1 GC,平衡吞吐量和延遲
    • 對延遲敏感的在線服務:選擇ZGC或Shenandoah,獲得更低的延遲
  2. 內存大小:

    • 小內存(<4GB):Parallel GC或G1 GC
    • 中等內存(4GB-32GB):G1 GC或Shenandoah
    • 大內存(>32GB):ZGC
  3. Java版本:

    • Java 8:Parallel GC(默認)或CMS
    • Java 11:G1 GC(默認)或嘗試實驗性的ZGC
    • Java 17及以上:G1 GC(默認)、ZGC或Shenandoah(均為產品特性)

總結

JVM和JMM是Java開發者必須掌握的核心知識,它們直接影響著Java程序的性能、穩定性和可靠性。從Java 8到Java 21,JVM和JMM經歷了多次重要更新,包括永久代到元空間的轉變、垃圾回收器的演進、虛擬線程的引入等。
在實際開發和面試中,理解這些概念和變化不僅有助于編寫高質量的Java代碼,還能幫助我們更好地進行性能調優和問題排查。希望本文能夠幫助你在技術面試中脫穎而出,同時也能在日常工作中更加得心應手地處理JVM相關問題。

參考資料

  1. Oracle官方文檔:JDK 11 Release Notes
  2. Oracle官方文檔:JDK 17 Release Notes
  3. OpenJDK Wiki:ZGC
  4. Oracle官方文檔:Java Language Changes
  5. 《深入理解Java虛擬機》第3版,周志明著
  6. 《Java并發編程的藝術》,方騰飛、魏鵬、程曉明著

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/81681.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/81681.shtml
英文地址,請注明出處:http://en.pswp.cn/web/81681.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Docker 掛載卷并保存為容器

1 創建docker容器 使用鏡像osrf/ros:humble-desktop-full-jammy創建并運行容器 sudo docker run -it --name ros2_humble osrf/ros:humble-desktop-full-jammy /ros_entrypoint.sh bash docker run -it -v d:\docker\ros2_humble:/root/ros2_ws osrf/ros:humble-desktop-fu…

無人機飛手共享接單平臺

2025年&#xff0c;無人機已不再是“黑科技”的代名詞。從農田噴灑到外賣配送&#xff0c;從航拍攝影到醫療急救&#xff0c;無人機正以驚人的速度滲透我們的生活。而在這場變革中&#xff0c; “無人機飛手共享接單平臺” 成為連接技術與需求的核心樞紐。它不僅讓專業飛手輕松…

【Web應用】若依框架:基礎篇07功能詳解-定時任務

文章目錄 ?前言?一、講解過程?二、動手實操?總結 標題詳情作者JosieBook頭銜CSDN博客專家資格、阿里云社區專家博主、軟件設計工程師博客內容開源、框架、軟件工程、全棧&#xff08;,NET/Java/Python/C&#xff09;、數據庫、操作系統、大數據、人工智能、工控、網絡、程序…

8.8 Primary ODSA service without ODSA Portal

主要ODSA服務&#xff08;不使用ODSA門戶&#xff09; 以下場景描述如下情況&#xff1a; ? 主ODSA客戶端應用程序被允許用于該類型的主設備&#xff0c;且對終端用戶啟用&#xff08;已授權&#xff09;。 ? 服務提供商&#xff08;SP&#xff09;能夠在不涉及ODSA門戶Web服…

深度檢測與動態透明度控制 - 基于Babylon.js的遮擋檢測實現解析

首先貼出實現代碼&#xff1a; OcclusionFader.ts import { AbstractEngine, Material, type Behavior, type Mesh, type PBRMetallicRoughnessMaterial, type Scene } from "babylonjs/core"; import { OcclusionTester } from "../../OcclusionTester"…

openssl 使用生成key pem

好的&#xff0c;以下是完整的步驟&#xff0c;幫助你在 Windows 系統中使用 OpenSSL 生成私鑰&#xff08;key&#xff09;和 PEM 文件。假設你的 openssl.cnf 配置文件位于桌面。 步驟 1&#xff1a;打開命令提示符 按 Win R 鍵&#xff0c;打開“運行”對話框。輸入 cmd&…

音視頻之視頻壓縮及數字視頻基礎概念

系列文章&#xff1a; 1、音視頻之視頻壓縮技術及數字視頻綜述 一、視頻壓縮編碼技術綜述&#xff1a; 1、信息化與視頻通信&#xff1a; 什么是信息&#xff1a; 眾所周知&#xff0c;人類社會的三大支柱是物質、能量和信息。具體而言&#xff0c;農業現代化的支柱是物質&…

傳統數據表設計與Prompt驅動設計的范式對比:以NBA投籃數據表為例

引言&#xff1a;數據表設計方法的演進 在數據庫設計領域&#xff0c;傳統的數據表設計方法與新興的Prompt驅動設計方法代表了兩種截然不同的思維方式。本文將以NBA賽季投籃數據表(shots)的設計為例&#xff0c;深入探討這兩種方法的差異、優劣及適用場景。隨著AI技術在數據領…

XCTF-web-mfw

發現了git 使用GitHack下載一下源文件&#xff0c;找到了php源代碼 <?phpif (isset($_GET[page])) {$page $_GET[page]; } else {$page "home"; }$file "templates/" . $page . ".php";// I heard .. is dangerous! assert("strpos…

Prompt Tuning與自然語言微調對比解析

Prompt Tuning 與輸入提示詞自然語言微調的區別和聯系 一、核心定義與區別 維度Prompt Tuning(提示微調)輸入提示詞自然語言微調本質優化連續向量空間中的提示嵌入(不可直接閱讀)優化離散自然語言文本(人類可理解)操作對象模型輸入嵌入層的連續向量(如WordEmbedding)自…

LVS的DR模式部署

目錄 一、引言&#xff1a;高并發場景下的流量調度方案 二、LVS-DR 集群核心原理與架構設計 &#xff08;一&#xff09;工作原理與數據流向 數據包流向步驟3&#xff1a; &#xff08;二&#xff09;模式特性與53網絡要求 三、實戰配置&#xff1a;從9環境搭建到參數調整…

8種常見數據結構及其特點簡介

一、8種常見數據結構 1. 數組&#xff08;Array&#xff09; 簡介&#xff1a;數組是有序元素的序列&#xff0c;連續內存塊存儲相同類型元素&#xff0c;通過下標直接訪問。數組會為存儲的元素都分配一個下標&#xff08;索引&#xff09;&#xff0c;此下標是一個自增連續的…

通過mailto:實現web/html郵件模板喚起新建郵件并填寫內容

一、背景 在實現網站、html郵件模板過程中&#xff0c;難免會遇到需要通過郵箱向服務提供方發起技術支持等需求&#xff0c;因此&#xff0c;我們需要通過一個功能&#xff0c;能新建郵件并提供模板&#xff0c;提高溝通效率 二、mailto協議配置說明 參數描述mailto:nameema…

好用但不常用的Git配置

參考文章 文章目錄 tag標簽分支新倉庫默認分支推送 代碼合并沖突處理默認diff算法 tag標簽 默認是以字母順序排序&#xff0c;這會導致一些問題&#xff0c;比如0.5.101排在0.5.1000之后。為了解決這個問題&#xff0c;我們可以把默認排序改為數值排序 git config --global t…

第六十八篇 從“超市收銀系統崩潰”看JVM性能監控與故障定位實戰

目錄 引言&#xff1a;當技術問題遇上生活場景一、JVM的“超市貨架管理哲學”二、收銀員工具箱&#xff1a;JVM監控三板斧三、典型故障診斷實錄四、防患于未然的運維智慧五、結語&#xff1a;從故障救火到體系化防控 引言&#xff1a;當技術問題遇上生活場景 想象一個周末的傍…

tauri2項目打開某個文件夾,類似于mac系統中的 open ./

在 Tauri 2 項目中打開文件夾 在 Tauri 2 項目中&#xff0c;你可以使用以下幾種方法來打開文件夾&#xff0c;類似于 macOS 中的 open ./ 命令功能&#xff1a; 方法一&#xff1a;使用 shell 命令 use tauri::Manager;#[tauri::command] async fn open_folder(path: Strin…

編譯pg_duckdb步驟

1. 要求cmake的版本要高于3.17&#xff0c;可以通過下載最新的cmake的程序&#xff0c;然后設置.bash_profile的PATH環境變量&#xff0c;將最新的cmake的bin目錄放到PATH環境變量的最前面 2. g的版本要支持c17標準&#xff0c;否則會報 error ‘invoke_result in namespace ‘…

GO 語言中變量的聲明

Go 語言變量名由字母、數字、下劃線組成&#xff0c;其中首個字符不能為數字。Go 語言中關鍵字和保留字都不能用作變量名。Go 語言中的變量需要聲明后才能使用&#xff0c;同一作用域內不支持重復聲明。 并且 Go 語言的變量聲明后必須使用。 1. var 聲明變量 在 Go 語言中&…

windows和mac安裝虛擬機-詳細教程

簡介 虛擬機&#xff1a;Virtual Machine&#xff0c;虛擬化技術的一種&#xff0c;通過軟件模擬的、具有完整硬件功能的、運行在一個完全隔離的環境中的計算機。 在學習linux系統的時候&#xff0c;需要安裝虛擬機&#xff0c;在虛擬機上來運行操作系統&#xff0c;因為我使…

XCTF-web-Cat

嘗試輸入127.0.0.1 嘗試127.0.0.1;ls 試了很多&#xff0c;都錯誤&#xff0c;嘗試在url里直接輸入&#xff0c;最后發現輸入%8f報錯 發現了Django和DEBUG 根據Django的目錄&#xff0c;我們使用進行文件傳遞 嘗試?url/opt/api/database.sqlite3&#xff0c;找到了flag