JVM常見考題總結

1. JVM基礎概念

Q1: 什么是JVM?JVM、JRE、JDK的關系是什么?

答案:

  • JVM: Java虛擬機,Java程序的運行環境
  • JRE: Java運行時環境 = JVM + 核心類庫
  • JDK: Java開發工具包 = JRE + 編譯器 + 調試工具

關系: JDK > JRE > JVM

Q2: Java程序的執行過程是什么?

答案:

Java源碼(.java) → javac編譯 → 字節碼(.class) → JVM解釋/編譯 → 機器碼

2. JVM內存結構

Q3: JVM內存區域有哪些?各自的作用是什么?

答案:

  • 程序計數器: 記錄當前線程執行的字節碼行號
  • 虛擬機棧: 存儲方法調用的局部變量和操作數棧
  • 本地方法棧: 為Native方法服務
  • 堆內存: 存儲對象實例,分為新生代和老年代
  • 方法區: 存儲類信息、常量、靜態變量(Java 8后改為元空間)

Q4: 堆內存的結構是什么?

答案:

堆內存
├── 新生代 (1/3)
│   ├── Eden區 (8/10)
│   ├── Survivor0 (1/10)
│   └── Survivor1 (1/10)
└── 老年代 (2/3)

Q5: 什么是棧溢出?什么是堆溢出?

答案:

  • 棧溢出 (StackOverflowError): 方法調用層次太深,棧空間不足
  • 堆溢出 (OutOfMemoryError): 創建對象過多,堆空間不足

示例:

// 棧溢出
public void recursion() {
    recursion(); // 無限遞歸
}

// 堆溢出  
List<Object> list = new ArrayList<>();
while(true) {
    list.add(new Object()); // 不斷創建對象
}

3. 垃圾回收

Q6: 什么是垃圾回收?如何判斷對象可以被回收?

答案:
垃圾回收: 自動釋放不再使用的對象內存

判斷方法:

  • 引用計數法: 統計對象被引用的次數(有循環引用問題)
  • 可達性分析: 從GC Roots開始,不可達的對象可以回收

GC Roots包括:

  • 虛擬機棧中的引用
  • 方法區中的靜態引用
  • 方法區中的常量引用
  • 本地方法棧中的引用

Q7: 常見的垃圾回收算法有哪些?

答案:

  • 標記-清除: 標記垃圾對象,然后清除(產生內存碎片)
  • 標記-復制: 將存活對象復制到另一塊內存(適合新生代)
  • 標記-整理: 標記后將存活對象向一端移動(適合老年代)
  • 分代收集: 新生代用復制算法,老年代用標記-整理

Q8: 常見的垃圾回收器有哪些?

答案:

  • Serial GC: 單線程,適合小應用
  • Parallel GC: 多線程,適合吞吐量優先
  • CMS GC: 并發收集,適合響應時間優先
  • G1 GC: 低延遲,適合大堆內存
  • ZGC/Shenandoah: 超低延遲收集器

4. 類加載機制

Q9: 類加載的過程是什么?

答案:

加載 → 驗證 → 準備 → 解析 → 初始化 → 使用 → 卸載

詳細說明:

  • 加載: 將.class文件讀入內存
  • 驗證: 檢查字節碼格式和語義
  • 準備: 為靜態變量分配內存并設置默認值
  • 解析: 將符號引用轉換為直接引用
  • 初始化: 執行靜態代碼塊和靜態變量賦值

Q10: 什么是雙親委派模型?

答案:
定義: 類加載器收到加載請求時,先委派給父加載器,父加載器無法加載時才自己加載

三層結構:

Bootstrap ClassLoader (啟動類加載器)
    ↑
Extension ClassLoader (擴展類加載器)  
    ↑
Application ClassLoader (應用類加載器)

優點:

  • 保證Java核心類庫的安全性
  • 避免類的重復加載

Q11: 什么時候會觸發類的初始化?

答案:

  • 創建類的實例
  • 訪問類的靜態變量或方法
  • 反射調用類
  • 初始化子類時先初始化父類
  • JVM啟動時的主類

5. JVM調優

Q12: 常用的JVM參數有哪些?

答案:
內存設置:

-Xms512m          # 初始堆大小
-Xmx2g            # 最大堆大小
-Xmn256m          # 新生代大小
-XX:MetaspaceSize=128m  # 元空間初始大小

垃圾回收器:

-XX:+UseG1GC      # 使用G1收集器
-XX:+UseConcMarkSweepGC  # 使用CMS收集器
-XX:+UseParallelGC       # 使用Parallel收集器

調試參數:

-XX:+PrintGC           # 打印GC信息
-XX:+HeapDumpOnOutOfMemoryError  # OOM時生成堆轉儲

Q13: 如何分析和解決內存泄漏?

答案:
分析工具:

  • jstat: 監控GC情況
  • jmap: 生成堆轉儲文件
  • MAT/VisualVM: 分析堆轉儲文件

常見內存泄漏:

  • 靜態集合持有對象引用
  • 監聽器未正確移除
  • 數據庫連接未關閉
  • ThreadLocal未清理

Q14: 如何進行JVM性能調優?

答案:
調優步驟:

  1. 監控應用性能指標
  2. 分析GC日志
  3. 調整堆內存大小
  4. 選擇合適的垃圾回收器
  5. 調整GC參數
  6. 驗證調優效果

調優原則:

  • 優先調整內存大小
  • 選擇合適的垃圾回收器
  • 減少Full GC頻率
  • 平衡吞吐量和延遲

6. 實際問題

Q15: 解釋以下代碼的執行結果

public class Test {
    static {
        System.out.println("靜態代碼塊");
    }
    
    {
        System.out.println("實例代碼塊");
    }
    
    public Test() {
        System.out.println("構造方法");
    }
    
    public static void main(String[] args) {
        new Test();
        new Test();
    }
}

答案:

靜態代碼塊
實例代碼塊
構造方法
實例代碼塊
構造方法

解釋: 靜態代碼塊只在類初始化時執行一次,實例代碼塊在每次創建對象時執行

Q16: String s = new String("abc")創建了幾個對象?

答案:
可能創建1個或2個對象:

  • 如果字符串常量池中已有"abc",創建1個對象(堆中的String對象)
  • 如果字符串常量池中沒有"abc",創建2個對象(常量池中的"abc" + 堆中的String對象)

Q17: 什么是內存屏障?volatile關鍵字的作用是什么?

答案:
內存屏障: 防止指令重排序的機制

volatile作用:

  • 保證可見性:修改對所有線程立即可見
  • 禁止指令重排序
  • 不保證原子性

7. 高級話題

Q18: 什么是逃逸分析?

答案:
逃逸分析: 分析對象的作用域,判斷對象是否會"逃逸"出方法或線程

優化效果:

  • 棧上分配:不逃逸的對象可以在棧上分配
  • 標量替換:將對象拆分為基本類型
  • 同步消除:消除不必要的同步操作

Q19: 什么是TLAB?

答案:
TLAB (Thread Local Allocation Buffer): 線程本地分配緩沖區

作用:

  • 每個線程在Eden區有自己的分配空間
  • 避免多線程分配對象時的競爭
  • 提高對象分配效率

Q20: JVM如何處理異常?

答案:
異常處理機制:

  • 異常表:記錄try-catch塊的范圍和處理器
  • 棧展開:異常發生時逐層向上查找處理器
  • 異常對象:在堆中創建異常對象

性能影響:

  • 異常創建成本高(需要填充棧軌跡)
  • 不要用異常控制正常流程