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块的范围和处理器
  • 栈展开:异常发生时逐层向上查找处理器
  • 异常对象:在堆中创建异常对象

性能影响:

  • 异常创建成本高(需要填充栈轨迹)
  • 不要用异常控制正常流程