第一节 java面试中,关于jvm的常问面试题有哪些?

亮子 2025-08-29 09:19:57 48 0 0 0

当然,JVM 是 Java 面试中绝对的核心领域,几乎必问。下面我为你梳理了从基础到高阶的常问面试题,并附上一些回答要点,希望能帮助你系统性地准备。


一、JVM 内存区域(运行时数据区) - 最最基础,必问

这是理解所有 JVM 问题的基础,必须倒背如流。

  1. 请说一下 JVM 的主要组成部分及其作用?

    • 类加载子系统(ClassLoader):负责加载.class文件。
    • 运行时数据区(Runtime Data Area):即我们常说的JVM内存结构。
    • 执行引擎(Execution Engine):解释/编译字节码为机器码并执行。
    • 本地方法接口(Native Interface):调用本地(C/C++)方法库。
    • 本地方法库(Native Libraries)**
  2. 详细说一下运行时数据区(内存区域)?

    • 线程私有
      • 程序计数器(PC Register):当前线程所执行的字节码的行号指示器。**唯一一个没有 OutOfMemoryError 的区域**。
      • Java 虚拟机栈(VM Stack):描述 Java 方法执行的内存模型,存储局部变量表、操作数栈、动态链接、方法出口等。会抛出 StackOverflowErrorOutOfMemoryError
      • 本地方法栈(Native Method Stack):为 Native 方法服务。与虚拟机栈类似。
    • 线程共享
      • 堆(Heap):**几乎所有对象实例和数组**都在这里分配内存。是 GC 管理的主要区域。会抛出 OutOfMemoryError
      • 方法区(Method Area):存储已被虚拟机加载的**类信息、常量、静态变量、即时编译器编译后的代码**等数据。JDK 8 之前叫“永久代”(PermGen),之后叫“元空间”(Metaspace)。也会抛出 OutOfMemoryError
      • 运行时常量池(Runtime Constant Pool):是方法区的一部分,存放编译期生成的各种**字面量**和**符号引用**。
  3. 一个对象创建的过程是怎样的?(结合内存区域回答)

      1. 类加载检查:检查 new 指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个类是否已被加载、解析和初始化过。
      1. 分配内存:在堆中为新生对象分配内存(方式有“指针碰撞”和“空闲列表”)。
      1. 初始化零值:将分配到的内存空间都初始化为零值(不包括对象头)。
      1. 设置对象头:存储对象的元数据(如哈希码、GC分代年龄、锁状态标志等)和类型指针(指向它的类元数据)。
      1. 执行 init 方法:按照程序员的意愿进行初始化(构造方法)。

二、垃圾回收(GC) - 核心中的核心

  1. 如何判断对象是否可以被回收?

    • 引用计数法(Java未采用):存在循环引用问题。
    • 可达性分析算法(根搜索算法):从一系列称为 GC Roots 的对象作为起点,向下搜索,走过的路径叫“引用链”。如果一个对象到 GC Roots 没有任何引用链相连,则证明此对象不可用。
    • GC Roots 包括:虚拟机栈中引用的对象、本地方法栈中引用的对象、方法区中静态属性引用的对象、方法区中常量引用的对象等。
  2. Java 的引用类型有哪些?

    • 强引用(Strong Reference):最常见的引用,Object obj = new Object()。只要强引用存在,GC就永远不会回收被引用的对象。
    • 软引用(Soft Reference):有用但非必需的对象。**内存不足时**会被GC回收。常用于缓存。
    • 弱引用(Weak Reference):非必需对象。**下一次GC时**无论内存是否足够都会被回收。
    • 虚引用(Phantom Reference):最弱的引用。无法通过它获取对象实例,其唯一目的是**为了在这个对象被GC回收时收到一个系统通知**。
  3. 说一下常见的垃圾收集算法?

    • 标记-清除(Mark-Sweep):先标记所有需要回收的对象,标记完成后统一回收。**问题:效率不高,产生内存碎片**。
    • 复制(Copying):将内存分为大小相等的两块,每次只使用一块。当一块用完了,就将还存活的对象复制到另一块上,然后把已使用的内存空间一次清理掉。**优点:没有碎片。缺点:内存利用率只有一半**。**常用于新生代**(Eden, S0, S1)。
    • 标记-整理(Mark-Compact):标记过程与“标记-清除”一样,但后续不是直接清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。**优点:没有碎片。缺点:效率偏低**。**常用于老年代**。
    • 分代收集(Generational Collection):**当前商业虚拟机的通用算法**。根据对象存活周期的不同将堆划分为**新生代**和**老年代**,然后根据各自的特点采用不同的收集算法。
  4. 说一下 JVM 中的垃圾收集器?(常问组合)

    • Serial / Serial Old:单线程,新生代采用复制算法,老年代采用标记-整理算法。Client模式下的默认收集器。
    • ParNew:Serial 的多线程版本。是许多Server模式下的虚拟机中**新生代**的首选收集器,因为只有它能与 CMS 配合工作。
    • Parallel Scavenge / Parallel Old:JDK8的**默认**组合。吞吐量优先的收集器。新生代复制,老年代标记-整理。
    • CMS(Concurrent Mark Sweep):以**获取最短回收停顿时间**为目标。标记-清除算法。过程复杂,分为4步。**问题:产生碎片,对CPU资源敏感**。
    • G1(Garbage-First):JDK9及以后的**默认**收集器。面向服务端应用。它将堆划分为多个大小相等的 Region,可预测的停顿时间模型是其核心优势。整体上看是“标记-整理”,局部(两个Region之间)上看是“复制”算法。
    • ZGC / Shenandoah:超低停顿(<10ms)的新一代收集器,适用于超大堆内存。

三、类加载机制

  1. 说一下类加载的过程?

    • 加载(Loading):通过全限定名获取二进制字节流,将静态存储结构转化为方法区的运行时数据结构,在堆中生成一个代表这个类的 Class 对象。
    • 验证(Verification):确保Class文件的字节流符合当前虚拟机要求,不会危害虚拟机自身安全。
    • 准备(Preparation):为**类变量**(static变量)分配内存并设置**初始零值**(如0, false, null等)。**注意:static final 修饰的常量(ConstantValue)会在此阶段被直接赋值为程序设定的值**。
    • 解析(Resolution):将常量池内的符号引用替换为直接引用。
    • 初始化(Initialization):执行类构造器 <clinit>() 方法的过程,真正开始执行类中定义的Java代码(如静态变量的赋值、静态代码块)。
  2. 什么是双亲委派模型?它的好处是什么?

    • 模型:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载,而是把这个请求**委派给父类加载器**去完成。只有当父加载器反馈自己无法完成时,子加载器才会尝试自己去加载。
    • 层次结构(从上到下):
      1. 启动类加载器(Bootstrap ClassLoader):加载 JAVA_HOME/lib 下的核心类库。
      2. 扩展类加载器(Extension ClassLoader):加载 JAVA_HOME/lib/ext 下的类。
      3. 应用程序类加载器(Application ClassLoader):加载用户类路径(ClassPath)上的类库。
      4. (自定义类加载器)
    • 好处
      • 避免类的重复加载
      • 保证Java核心API的安全(如用户自定义一个java.lang.Object类,双亲委派会最终委派给启动类加载器去加载核心库里的Object,从而防止核心API被篡改)。
  3. 什么情况下会破坏双亲委派?如何破坏?

    • 历史原因:JDK1.2之前已有用户自定义类加载器,为了兼容,引入了findClass方法让用户重写,而不是重写loadClass(破坏委派逻辑的方法)。
    • SPI机制:Java核心库(如JDBC)的接口由Bootstrap加载器加载,但其实现类(各数据库驱动)由线程上下文类加载器(TCCL)去加载,这是一种父加载器请求子加载器完成加载的行为,打破了双亲委派。
    • 热部署、热替换:如OSGi、Tomcat,每个模块(Bundle/WEB-INF)都有自己的类加载器,需要动态地加载和卸载类。

四、性能调优与工具 - 体现实践能力

  1. 常用的 JVM 调优参数有哪些?

    • 堆内存相关
      • -Xms:初始堆大小(如 -Xms256m
      • -Xmx:最大堆大小(如 -Xmx1g
      • -Xmn:新生代大小(如 -Xmn512m
      • -XX:NewRatio:老年代/新生代的比例(如 -XX:NewRatio=2 表示老年代是新生代的2倍)
      • -XX:SurvivorRatio:Eden区/Survivor区的比例(如 -XX:SurvivorRatio=8
    • 方法区(元空间)相关
      • -XX:MetaspaceSize:初始元空间大小
      • -XX:MaxMetaspaceSize:最大元空间大小(默认无限制,但受物理内存限制)
    • GC 日志相关
      • -XX:+PrintGCDetails:打印GC详细日志
      • -Xloggc:gc.log:将GC日志输出到文件
    • 其他
      • -XX:+HeapDumpOnOutOfMemoryError:在OOM时自动生成堆转储文件(hprof文件),**非常重要**!
  2. 你常用的 JVM 性能监控和故障处理工具有哪些?

    • 命令行工具(JDK自带):
      • jps:查看Java进程。
      • jstat:查看JVM统计信息,如GC情况(jstat -gc)。
      • jinfo:查看和修改虚拟机参数。
      • jmap:生成堆转储快照(jmap -dump:format=b,file=heap.hprof)。
      • jstack:生成线程快照,用于**排查死锁、死循环、线程阻塞**(jstack)。
    • 可视化工具
      • jconsole:Java监控和管理控制台。
      • VisualVM:功能强大的多合一故障诊断工具。
      • 第三方工具:Arthas(阿里开源,强烈推荐)、MAT(内存分析工具)、JProfiler等。
  3. 如何排查 OOM(OutOfMemoryError)问题?

      1. 通过 -XX:+HeapDumpOnOutOfMemoryError 参数让JVM在发生OOM时自动生成堆转储文件(.hprof)。
      1. 使用 MAT(Memory Analyzer Tool)JProfiler 等工具分析这个 .hprof 文件。
      1. 查看**占用内存最大的对象**是哪些。
      1. 查看这些对象的 GC Roots 引用链,找到是谁在持有这些对象导致无法被回收(比如是一个静态Map、连接未关闭等)。
      1. 结合代码定位问题并修复。

总结

准备 JVM 面试时,建议按照这个脉络来:

  1. 基础:内存区域 -> 对象创建 -> 垃圾回收(判断死亡 -> 算法 -> 收集器)。
  2. 进阶:类加载过程 -> 双亲委派 -> 破坏双亲委派的场景。
  3. 实践:常用参数 -> 监控工具 -> 排查OOM/死锁的步骤。

不仅要知其然,还要知其所以然。面试官很喜欢追问“为什么”,比如“为什么要有Surviv区?”“G1相比CMS好在哪里?”。祝你面试顺利!