Java面试-02-JVM虚拟机

JVM虚拟机面试题(完整版)

目录

  • [1. JVM主要组成部分及作用](#1. JVM主要组成部分及作用)
  • [2. 类加载器](#2. 类加载器)
    • [2.1 类加载器分类](#2.1 类加载器分类)
    • [2.2 类加载机制及过程](#2.2 类加载机制及过程)
    • [2.3 双亲委派机制](#2.3 双亲委派机制)
  • [3. 运行时数据区](#3. 运行时数据区)
  • [4. 本地方法接口](#4. 本地方法接口)
  • [5. JVM垃圾回收](#5. JVM垃圾回收)
    • [5.1 垃圾回收机制](#5.1 垃圾回收机制)
    • [5.2 对象回收判断算法](#5.2 对象回收判断算法)
    • [5.3 垃圾回收算法](#5.3 垃圾回收算法)
    • [5.4 Java堆分代模型](#5.4 Java堆分代模型)
    • [5.5 垃圾回收器](#5.5 垃圾回收器)
    • [5.6 对象分配与回收策略](#5.6 对象分配与回收策略)
    • [5.7 对象流转完整过程](#5.7 对象流转完整过程)
  • [6. JVM常见异常及排查](#6. JVM常见异常及排查)
  • [7. JVM调优](#7. JVM调优)
    • [7.1 JVM调优步骤](#7.1 JVM调优步骤)
    • [7.2 调优核心原则](#7.2 调优核心原则)
    • [7.3 可视化监控工具](#7.3 可视化监控工具)
    • [7.4 实际项目调优案例](#7.4 实际项目调优案例)
  • [8. 高频面试问题](#8. 高频面试问题)

1. JVM主要组成部分及作用

JVM核心分为两大子系统两大组件

两大子系统

  1. 类加载器子系统 :负责将.class字节码文件加载到JVM内存中
  2. 执行引擎子系统:执行加载后类中的字节码指令,会先编译为机器码再执行

两大组件

  1. 运行时数据区:JVM内存区域,存储程序运行数据
  2. 本地方法接口 :与native本地方法交互的接口

2. 类加载器

2.1 类加载器分类

  1. 启动类加载器(Bootstrap) :加载Java核心类库(rt.jar
  2. 扩展类加载器(Extension):加载JDK扩展目录下的jar包
  3. 应用类加载器(App) :加载项目classpath下的类文件
  4. 自定义类加载器:加载自定义路径的JAR/类文件

2.2 类加载机制及过程

类加载完整流程:加载 → 连接 → 初始化

  1. 加载 :通过类加载器读取.class文件,加载到JVM内存
  2. 连接
    • 校验:验证字节码文件的安全性、正确性
    • 准备:为静态变量分配内存,设置默认初始值
    • 解析:将常量池符号引用转换为直接引用
  3. 初始化:执行静态代码块,为静态变量赋实际值

2.3 双亲委派机制

  • 定义:类加载器收到加载请求时,优先委托父类加载器执行,递归向上直到启动类加载器;父类无法加载时,子类才尝试加载
  • 核心优势:避免类重复加载,保证核心类安全

3. 运行时数据区

JVM内存模型分为5个区域,按线程共享/私有划分:

区域名称 作用 存储内容 线程特性
堆(Heap) JVM最大内存区域,GC主要区域 对象实例、数组 共享
方法区 存储类元数据 类信息、常量、静态变量、编译后代码 共享
虚拟机栈 方法执行内存模型 局部变量表、操作数栈、方法返回地址 私有
程序计数器 记录线程执行字节码行号 当前执行指令地址 私有
本地方法栈 执行native方法服务 本地方法调用信息 私有

4. 本地方法接口

  • 作用 :用于JVM与底层操作系统、本地native方法进行交互
  • 场景:调用C/C++实现的底层系统功能

5. JVM垃圾回收

5.1 垃圾回收机制

  1. 自动回收堆中无引用、不可达的垃圾对象,释放内存
  2. System.gc():手动触发GC(不保证立即执行)
  3. finalize():GC回收对象前自动调用的方法

5.2 对象回收判断算法

  1. 引用计数法
    • 给对象添加计数器,引用+1,失效-1;计数器=0可回收
    • 缺点:无法解决对象循环引用问题
  2. 可达性分析算法 (JVM默认)
    • GC Roots为起点,遍历引用链,不可达对象可回收
    • 可作为GC Roots的对象
      1. 虚拟机栈中引用的对象
      2. 方法区静态变量引用的对象
      3. 方法区常量引用的对象
      4. 本地方法栈引用的对象

5.3 垃圾回收算法

算法 原理 优点 缺点
标记-清除 先标记存活对象,再清除垃圾 简单 产生内存碎片
复制算法 内存分两块,存活对象复制到空块,清空原块 无碎片 浪费50%内存
标记-整理 标记后将存活对象移到一端,清理边界外内存 无碎片 移动对象开销大
分代收集 新生代用复制,老年代用标记清除/整理 综合效率最高 逻辑复杂

5.4 Java堆分代模型

新生代(对象存活率低)
  • 分区:Eden : From Survivor : To Survivor = 8:1:1
    新生代分为三个区域,分别是一个 Eden 区和两个 Survivor 区,它们的默认比例是 8:1:1。Eden 区:新创建的对象首先会被分配到 Eden 区。当 Eden 区满时,会触发一次 Minor GC(新生代垃圾回收)。Survivor 区:在 Minor GC 时,Eden 区中存活的对象会被移动到其中一个 Survivor 区。同时,From Space 中之前存活的对象,如果经过这次垃圾回收仍然存活,并且年龄达到一定阈值(默认是 15,可以通过-XX:MaxTenuringThreshold参数调整),会被移动到老年代;如果未达到阈值,则会和 Eden 区移动过来的对象一起被复制到另一个 Survivor 区。之后,两个Survivor 的角色会互换
  • 垃圾回收:Minor GC,频率高、速度快
  • 算法:复制算法
老年代(对象存活率高)
  • 存储:长期存活对象、大对象
    年龄阈值:对象在 Survivor 区经过多次 Minor GC 后,年龄达到一定阈值,会被晋升到老年代。大对象直接进入:当创建的对象占用内存超过一定大小(可以通过-XX:PretenureSizeThreshold参数设置)时,会直接在老年代分配内存。Survivor 区空间不足:如果在 Survivor 区中,相同年龄的所有对象大小总和大于 Survivor区空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。
  • 垃圾回收:Full GC/Major GC,频率低、速度慢
  • 算法:标记-清除 / 标记-整理
元空间(Java8+,替代永久代)
  • 永久代(Java 7 及以前)存储内容:主要存储类的元数据信息(如类的结构、方法、字段等)、常量池、静态变量等。永久代的大小在启动时就需要指定,且不能动态扩展。问题:由于永久代的大小是固定的,如果加载的类过多,或者常量池过大,容易导致OutOfMemoryError:PermGenspace异常。元空间(Java 8 及以后)存储内容:同样用于存储类的元数据信息,但与永久代不同的是,元空间使用的是本地内存(Native Memory),而不是 JVM 堆内存。优点:元空间的大小可以动态扩展,只要系统的本地内存足够,就不会出现永久代那样的内存溢出问题。
  • 存储:类元数据、常量池

5.5 垃圾回收器

新生代收集器
  1. Serial:单线程、复制算法、停顿用户线程
  2. ParNew:Serial多线程版本
  3. Parallel Scavenge :关注高吞吐量
老年代收集器
  1. Serial Old:Serial老年代版本,标记-整理
  2. Parallel Old:多线程、标记-整理
  3. CMS:最短回收停顿,并发回收
整堆收集器
  • G1:跨代收集,兼顾吞吐量与停顿时间

5.6 对象分配与回收策略

  1. 新对象优先分配在Eden区
  2. 大对象直接进入老年代
  3. 长期存活对象晋升老年代(默认年龄阈值15)
  4. Eden区满 → Minor GC
  5. 老年代满 → Full GC

5.7 对象流转完整过程

  1. 创建 → 分配到Eden区
  2. Eden满 → Minor GC → 存活对象进入Survivor
  3. Survivor中对象年龄递增 → 达标晋升老年代
  4. 老年代满 → Full GC回收整个堆
  5. 无引用 → 被GC彻底回收

6. JVM常见异常及排查

StackOverflowError(栈溢出)

  • 原因:无限递归、方法调用层级过深、大量局部变量
  • 排查:检查递归逻辑、方法调用链

OutOfMemoryError(堆溢出/OOM)

  • 原因:加载数据过大、集合未释放、死循环创建对象、内存参数过小
  • 排查:分析dump文件、检查对象引用、优化代码

7. JVM调优

7.1 JVM调优步骤

  1. 监控:使用JConsole、VisualVM查看GC日志、堆内存快照
  2. 分析 :看日志,判断GC频率、停顿时间是否异常
    Minor GC执行时间不到50ms;Minor GC执行不频繁,约10秒一次;Full GC执行时间不到1s;Full GC执行频率不算频繁,不低于10分钟1次;
  3. 调整:修改内存参数、GC收集器、分代比例
  4. 验证:持续监控,找到最优参数

7.2 调优核心原则

  1. -Xms(初始堆)与-Xmx(最大堆)设置为相同值,减少GC次数
  2. 新生代:老年代默认比例 1:2,可根据场景调整
  3. 高并发场景优先使用并行收集器/G1
  4. 避免大对象直接进入老年代

7.3 可视化监控工具

  • JConsole:JDK自带,监控内存、线程、GC
  • VisualVM:功能全面的JVM分析工具
  • MAT:分析OOM dump文件,定位内存泄漏

7.4 实际项目调优案例

项目场景 :知识图谱工具部署后响应慢、频繁OOM

问题原因 :数据导入创建大量临时大对象,堆内存不足

调优过程

  1. 增大堆内存:-Xms4g -Xmx4g
  2. 调整分代比例:-XX:NewRatio=1(新生代:老年代=1:1)
  3. 启用GC收集器:-XX:+UseG1GC
  4. 开启dump:-XX:+HeapDumpOnOutOfMemoryError
  5. 代码优化:使用对象池、重构导入逻辑(CSV导入替代内存操作)
    最终方案:代码重构+合理JVM参数,解决OOM问题

8. 高频面试问题

  1. 对象一定分配在堆中吗?

    不一定。无逃逸对象可在栈上分配 ;简单对象可通过标量替换拆分存储。

  2. Minor GC和Full GC区别?

    • Minor GC:新生代回收,速度快、频率高
    • Full GC:整堆回收,速度慢、频率低
  3. 永久代和元空间区别?

    • 永久代:Java7及以前,使用堆内存,固定大小
    • 元空间:Java8+,使用本地内存,可动态扩展
  4. CMS收集器特点?

    并发收集、低停顿、适用于互联网服务端