ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

【Java面试题】JVM

【Java面试题】JVM JVM 面试题汇总JVM 是 Java 后端面试中的重点内容之一常见考察方向包括JVM 内存结构、对象创建、对象内存布局、垃圾回收、类加载机制、双亲委派、JVM 调优等。本文按照JVM 内存结构 → 对象 → 类加载 → 垃圾回收 → 垃圾回收器 → JVM 调优的顺序进行整理适合 Java 后端面试前复习。一、JVM 基础1. 什么是 JVMJVM即 Java Virtual MachineJava 虚拟机。它是 Java 程序运行的基础环境主要负责加载 Java 字节码执行字节码管理内存垃圾回收提供运行时环境Java 程序的执行过程可以简单理解为Java 源代码 ↓ javac 编译 ↓ .class 字节码 ↓ JVM ↓ 机器指令 ↓ CPU 执行JVM 的核心优势之一就是Java 程序编译成与平台无关的字节码由不同平台上的 JVM 负责执行。因此可以实现一次编译到处运行。二、JVM 内存结构2. JVM 运行时数据区有哪些JVM 运行时数据区主要包括JVM 运行时数据区 │ ├── 程序计数器 │ ├── Java 虚拟机栈 │ ├── 本地方法栈 │ ├── 堆 │ └── 方法区其中线程私有程序计数器Java 虚拟机栈本地方法栈线程共享堆方法区三、程序计数器3. 什么是程序计数器程序计数器可以理解成当前线程正在执行的字节码指令地址。例如线程 A PC → 指令 10 线程 B PC → 指令 25每个线程都有自己的程序计数器。4. 为什么程序计数器是线程私有的因为 CPU 在多个线程之间切换时需要知道每个线程上次执行到了哪里。所以每个线程都需要独立保存自己的指令执行位置。5. 程序计数器会发生 OutOfMemoryError 吗不会。程序计数器是 JVM 规范中唯一没有规定OutOfMemoryError情况的区域。四、Java 虚拟机栈6. 什么是 Java 虚拟机栈每个线程都有一个独立的 Java 虚拟机栈。线程每调用一个方法就会创建一个栈帧 Stack Frame方法执行完成后对应的栈帧出栈。例如publicvoidmethodA(){methodB();}publicvoidmethodB(){}执行过程methodA() ↓ 栈帧 A ↓ methodB() ↓ 栈帧 B当methodB()执行完成栈帧 B 出栈然后methodA()继续执行。五、栈帧7. 栈帧中有什么一个栈帧主要包含栈帧 │ ├── 局部变量表 ├── 操作数栈 ├── 动态链接 └── 方法返回地址8. 局部变量表是什么用于保存方法参数局部变量基本数据类型对象引用例如publicvoidtest(){inta10;UserusernewUser();}栈帧中的局部变量表会保存a → 10 user → User 对象引用需要注意对象本身通常存储在堆中栈中保存的是对象引用。六、栈溢出9. 什么情况下会出现 StackOverflowError当线程请求的栈深度超过 JVM 允许的深度时会出现StackOverflowError最典型的例子就是无限递归publicvoidtest(){test();}执行过程test() ↓ test() ↓ test() ↓ test() ↓ ...不断创建栈帧最终栈空间耗尽。七、堆10. Java 堆是什么Java 堆是 JVM 中最大的一块内存区域之一。主要用于存放对象实例和数组。例如UserusernewUser();通常可以理解为栈 │ │ user 引用 ↓ 堆 ┌───────────┐ │ User 对象 │ └───────────┘堆也是垃圾回收器主要管理的区域。八、堆为什么要分新生代和老年代对象具有明显的生命周期特征大量对象创建后很快就会死亡少量对象会长期存活。因此 JVM 通常会根据对象存活时间进行分代管理。经典的堆结构Java Heap │ ├── 新生代 Young Generation │ │ │ ├── Eden │ ├── Survivor │ └── Survivor │ └── 老年代 Old Generation九、对象为什么优先分配到 Eden例如UserusernewUser();一般情况下新创建的对象首先会分配到Eden 区。当 Eden 空间不足时会触发 Minor GC。存活下来的对象会进入 Survivor 区并根据 GC 次数逐渐晋升到老年代。十、对象晋升老年代经典的对象晋升过程Eden ↓ Minor GC Survivor ↓ 多次 GC 后仍然存活 Survivor ↓ Old对象年龄达到一定阈值后可以晋升到老年代。但需要注意实际 JVM 的对象晋升策略比简单的“年龄达到 15 次就进入老年代”更加复杂可能受到动态年龄判定、大对象分配等因素影响。十一、方法区11. 什么是方法区方法区是 JVM 规范定义的运行时数据区域。主要用于存储类信息方法信息字段信息运行时常量池静态变量等十二、永久代和元空间12. 永久代和元空间有什么区别JDK 8 之前方法区 ↓ 永久代 PermGenJDK 8 开始方法区 ↓ 元空间 Metaspace最大的区别永久代使用 JVM 堆内存而元空间主要使用本地内存。所以 JDK 8 中Heap ├── Eden ├── Survivor └── Old Native Memory └── Metaspace十三、运行时常量池13. 什么是运行时常量池运行时常量池属于方法区的一部分。主要用于存放类和接口的名称字段名称方法名称常量符号引用等例如StringnameTom;字符串相关的常量信息会涉及运行时常量池和字符串常量池。十四、String 常量池14. 什么是 String 常量池为了避免大量创建相同字符串对象JVM 会维护字符串常量池。例如Strings1hello;Strings2hello;通常s1 s2结果为true因为两个变量引用的是同一个字符串常量。15. new String(“hello”) 创建几个对象经典面试题StringsnewString(hello);通常从概念上可以理解为可能涉及两个对象hello ↓ 字符串常量池对象 new String(...) ↓ 堆中的 String 对象但具体对象数量要结合字符串常量池中是否已经存在hello来判断。面试时不要机械回答“一定两个”。十六、对象创建过程16. new 一个对象发生了什么例如UserusernewUser();大致过程new ↓ 类加载检查 ↓ 分配内存 ↓ 初始化零值 ↓ 设置对象头 ↓ 执行构造方法 ↓ 返回对象引用可以总结成类加载检查 → 分配内存 → 初始化零值 → 设置对象头 → 执行构造方法。十七、对象内存布局17. 一个对象在内存中包含什么HotSpot 中通常可以从三个部分理解对象 │ ├── 对象头 │ ├── 实例数据 │ └── 对齐填充18. 对象头有什么对象头主要包含Mark Word用于保存哈希码GC 年龄锁状态锁相关信息等类型指针指向对象所属的类信息。十九、指针压缩19. 什么是指针压缩64 位 JVM 中普通对象指针可能占用 64 bit。为了节省内存HotSpot 提供Compressed Oops即压缩普通对象指针。在一定条件下可以把对象引用压缩成 32 bit。这可以减少对象占用的内存同时提高 CPU Cache 命中率。二十、垃圾回收20. 为什么需要垃圾回收Java 不需要像 C/C 一样手动释放对象内存。JVM 会自动判断哪些对象已经没有使用价值然后回收它们占用的内存。这就是Garbage CollectionGC。二十一、如何判断对象是否可以被回收主要有两种思路① 引用计数法每个对象维护一个引用计数器。对象 A ↑ 引用 1 对象 A ↑ 引用 2当引用为 0 时可以回收。但是引用计数无法解决循环引用问题。例如A → B ↑ ↓ └───┘即使外部已经没有引用A → B → A引用计数仍然不为 0。所以 JVM 主要使用可达性分析算法。二十二、可达性分析21. 什么是可达性分析从 GC Roots 开始向下搜索。如果对象从 GC Roots 出发不可达那么就说明这个对象已经没有被使用的可能可以被回收。例如GC Roots │ ↓ 对象 A │ ↓ 对象 BA、B 都是可达对象。而对象 C没有任何 GC Root 可以到达那么 C 就可能成为垃圾。二十三、什么是 GC Roots常见 GC Roots 包括虚拟机栈中的局部变量引用方法区中的静态变量引用方法区中的常量引用JNI 本地方法栈中的引用JVM 内部的一些对象简单理解GC Roots 是垃圾回收进行可达性分析时的起点。二十四、垃圾回收算法常见垃圾回收算法标记-清除 标记-复制 标记-整理二十五、标记-清除过程第一步标记垃圾对象 第二步清除垃圾对象例如[A][垃圾][B][垃圾][C] ↓ [A][空闲][B][空闲][C]缺点容易产生内存碎片。二十六、标记-复制把内存划分成两块From To存活对象复制到另一块区域。例如From [A][垃圾][B][垃圾] ↓ To [A][B]然后清空 From。优点没有内存碎片。缺点需要额外空间。所以非常适合存活对象较少的新生代。二十七、标记-整理首先标记垃圾对象。然后将存活对象移动到内存的一侧[A][垃圾][B][垃圾][C] ↓ [A][B][C][空闲空间]相比标记-清除可以减少内存碎片。常用于老年代。二十八、Minor GC、Major GC、Full GC22. 什么是 Minor GC主要针对新生代。因为新生代对象死亡率比较高所以 Minor GC 通常比较频繁。23. 什么是 Full GCFull GC 通常会涉及整个堆以及方法区等区域成本较高。因此Full GC 过于频繁通常意味着 JVM 存在内存压力或配置问题。面试时不要简单认为“Full GC 就一定等于整个堆完全回收。”具体行为取决于垃圾收集器和 JVM 实现。二十九、Stop-The-World24. 什么是 STWSTWStop-The-World。垃圾回收过程中为了保证对象状态的一致性JVM 可能暂停应用线程。例如业务线程 ────────暂停────────→ 继续 ↑ │ GCSTW 时间越长应用响应延迟越明显。因此现代垃圾收集器都会尽量降低 STW 时间。三十、常见垃圾收集器常见垃圾收集器包括Serial ParNew Parallel CMS G1 ZGC Shenandoah实际面试中重点关注CMS、G1、ZGC。三十一、CMSCMSConcurrent Mark Sweep。主要目标降低老年代垃圾回收时的停顿时间。主要过程初始标记 ↓ 并发标记 ↓ 重新标记 ↓ 并发清除其中初始标记STW并发标记与用户线程并发重新标记STW并发清除与用户线程并发CMS 的主要缺点使用标记-清除算法会产生内存碎片。CMS 已经在现代 JDK 中被淘汰/移除因此更多作为 JVM 历史知识和面试题出现。三十二、G125. 什么是 G1G1Garbage First。G1 将整个堆划分成大量大小相等的 RegionHeap ┌───┬───┬───┬───┐ │ R │ R │ R │ R │ ├───┼───┼───┼───┤ │ R │ R │ R │ R │ ├───┼───┼───┼───┤ │ R │ R │ R │ R │ └───┴───┴───┴───┘不再严格要求一块区域必须属于 Eden、Survivor 或 Old。一个 Region 可以根据实际情况扮演不同角色。26. G1 为什么叫 Garbage FirstG1 会根据 Region 中垃圾最多、回收收益最高的区域优先进行回收。所以叫Garbage First。三十三、G1 的特点G1 主要优势支持大堆内存可预测停顿时间Region 化管理可以混合回收年轻代和老年代因此G1 是现代 Java 应用中非常重要的垃圾收集器。三十四、ZGC27. ZGC 是什么ZGC 是一种低延迟垃圾收集器。主要目标在非常大的堆内存下仍然保持很低的 GC 停顿时间。它大量使用并发处理并通过着色指针、读屏障等技术降低 STW 时间。适用于对延迟非常敏感的大型 Java 应用。三十五、类加载机制28. 什么是类加载当 JVM 第一次使用某个类时需要将.class字节码加载到 JVM 中。整体过程加载 ↓ 验证 ↓ 准备 ↓ 解析 ↓ 初始化通常把验证 准备 解析称为连接。因此完整过程加载 → 连接 → 初始化三十六、加载29. 加载阶段做什么主要完成获取类的二进制字节流将字节流转换为方法区中的运行时数据结构创建对应的 Class 对象最终.class ↓ JVM ↓ Class 对象三十七、验证验证阶段主要保证字节码符合 JVM 规范不会危害 JVM 的安全。包括文件格式验证元数据验证字节码验证符号引用验证三十八、准备准备阶段为类的静态变量分配内存并设置默认值。例如publicstaticintcount10;准备阶段可能是count 0而不是count 10真正赋值10的过程属于初始化阶段。三十九、初始化初始化阶段才真正执行类中的初始化代码。例如publicstaticintcount10;最终count 10如果存在static{System.out.println(hello);}也会在初始化阶段执行。四十、类加载器30. JVM 中有哪些类加载器常见的Bootstrap ClassLoader ↓ Platform ClassLoader ↓ Application ClassLoaderBootstrap ClassLoader负责加载 Java 核心类。例如java.lang.*Platform ClassLoader负责加载平台相关类。Application ClassLoader负责加载应用程序 ClassPath 下的类。四十一、什么是双亲委派类加载器收到类加载请求后Application ClassLoader ↓ Platform ClassLoader ↓ Bootstrap ClassLoader先让父加载器尝试加载。父加载器加载不了再由子加载器加载所以叫双亲委派模型。四十二、为什么要使用双亲委派主要有两个作用。① 避免类重复加载父加载器已经加载过java.lang.String子加载器就不会再次加载。② 保证 Java 核心类安全例如有人自己写java.lang.String正常情况下不会让自定义类加载器优先加载这个类。因此可以防止核心 API 被随意替换。四十三、什么情况下会打破双亲委派典型例子Tomcat 类加载机制、SPI 等。例如 SPIJDBC ↓ DriverManager ↓ ServiceLoader ↓ 具体数据库驱动这里存在父加载器加载的代码需要调用子加载器加载的实现类。所以需要使用类似线程上下文类加载器等机制解决。面试时知道这个例子即可。四十四、JVM 调优31. JVM 出现 OOM 怎么排查如果出现java.lang.OutOfMemoryError: Java heap space可以按照以下思路排查出现 OOM ↓ 确认是哪一种 OOM ↓ 查看 JVM 参数 ↓ 查看堆使用情况 ↓ 分析 Heap Dump ↓ 定位大对象 / 内存泄漏 ↓ 优化代码或 JVM 参数常见工具jps jstat jmap jstack jcmd VisualVM MAT四十五、常见 JVM 参数32. 常见堆内存参数有哪些例如-Xms-Xmx分别表示-Xms → 初始堆大小 -Xmx → 最大堆大小例如-Xms512m-Xmx2g表示初始堆 512 MB最大堆 2 GB。33. 为什么一般建议 Xms 和 Xmx 设置成相同如果Xms XmxJVM 运行过程中可能需要不断扩展堆。如果设置Xms Xmx可以减少堆扩容相关的开销使堆大小更加稳定。但实际生产环境仍然需要结合具体应用和容器资源合理配置。四十六、如何排查 CPU 过高假设 Java 服务 CPU 突然达到 100%。可以1. top ↓ 找到高 CPU 进程 2. top -H -p PID ↓ 找到高 CPU 线程 3. 线程 ID 转成十六进制 4. jstack PID ↓ 找到对应线程 5. 分析线程正在执行什么代码核心思路操作系统定位进程 → 定位线程 → jstack 定位 Java 代码。四十七、如何排查内存泄漏常见思路发现内存持续上涨 ↓ 生成 Heap Dump ↓ MAT / VisualVM 分析 ↓ 查看对象数量和占用 ↓ 寻找 GC Roots ↓ 定位异常引用 ↓ 修复代码常见原因静态集合不断保存对象ThreadLocal 使用不当缓存没有设置过期策略监听器没有正确注销集合只增加不删除四十八、JVM 面试重点总结如果时间有限可以优先掌握下面这些。第一梯队必须掌握JVM 内存JVM 内存结构堆栈方法区程序计数器元空间栈帧对象对象创建过程对象内存布局对象头String 常量池对象晋升GCGC Roots可达性分析标记-清除标记-复制标记-整理Minor GCFull GCSTW类加载类加载过程类加载器双亲委派为什么需要双亲委派第二梯队重点理解G1CMSZGC元空间指针压缩OOM 排查CPU 过高排查Heap Dump 分析JVM 常用参数第三梯队了解即可TLAB逃逸分析标量替换JIT栈上分配字节码执行过程GC 日志分析Shenandoah四十九、JVM 知识体系最后可以把整个 JVM 知识体系总结成JVM │ ┌─────────────────┼─────────────────┐ ↓ ↓ ↓ 运行时数据区 类加载机制 执行引擎 │ │ │ ┌────┼────┐ ┌────┼────┐ ┌────┴────┐ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ 栈 堆 方法区 加载 双亲 初始化 解释执行 JIT │ │ │ ├── 新生代 │ └── 老年代 │ └── 栈帧 │ ├── 局部变量表 ├── 操作数栈 └── 返回地址 │ ↓ GC │ ┌───────────┼───────────┐ ↓ ↓ ↓ GC Roots 回收算法 垃圾收集器 │ ┌─────┼─────┐ ↓ ↓ ↓ 清除 复制 整理 │ G1 / ZGC五十、JVM 面试最重要的一条主线如果把 JVM 面试题压缩成一条主线可以这样理解Java 对象 ↓ 创建在哪里 ↓ 堆 ↓ 什么时候被回收 ↓ GC Roots 可达性分析 ↓ 怎么回收 ↓ 标记-清除 / 复制 / 整理 ↓ 谁负责回收 ↓ 垃圾收集器 ↓ 为什么 GC 会暂停程序 ↓ STW ↓ 如何降低停顿 ↓ G1 / ZGC另一条主线.class 文件 ↓ 类加载器 ↓ 加载 ↓ 验证 ↓ 准备 ↓ 解析 ↓ 初始化 ↓ Class 对象 ↓ JVM 执行因此JVM 不应该只靠死记硬背。真正理解下面这几个问题对象放在哪里对象什么时候可以被回收垃圾怎么回收类是怎么加载进 JVM 的JVM 出现 OOM、CPU 飙高时怎么排查基本就掌握了 JVM 面试的主干知识。总结对于 Java 后端实习面试来说JVM 不需要一开始就钻到 HotSpot 源码。建议学习顺序JVM 内存结构 ↓ 堆和栈 ↓ 对象创建与对象内存布局 ↓ GC Roots 可达性分析 ↓ 垃圾回收算法 ↓ G1 / ZGC ↓ 类加载机制 ↓ 双亲委派 ↓ JVM 调优与故障排查其中最值得深入理解的是JVM 内存结构 对象 GC 类加载这四部分是 JVM 面试题的核心。当面试官继续追问时再往JIT、逃逸分析、TLAB、GC 日志、G1 源码这些更底层的方向深入即可。
返回列表