Skip to content

大厂真题解析

定位: 真实面试题的全链路拆解——每一道题都展示如何串联 JVM 的不同知识点 使用方式: 先自己回答,再对比参考回答。关注"原理链"——每一问如何从 A 连到 B 再到 C


知识地图:JVM 知识体系全景

┌───────────────────────────────────────────────────────────────────────┐
│                        JVM 知识体系全景                                │
│                                                                       │
│  ┌──────────┐  ┌────────────┐  ┌────────────┐  ┌──────────────────┐ │
│  │ 01 字节码 │→ │ 02 类加载   │→ │ 03 运行时   │→ │ 04 对象分配与     │ │
│  │ .class    │  │ 加载/验证  │  │ 数据区      │  │ 内存布局          │ │
│  │ 结构      │  │ 双亲委派   │  │ 堆/栈/方法区│  │ TLAB/MarkWord    │ │
│  │ javap     │  │ 自定义CL   │  │ 直接内存    │  │ 逃逸分析         │ │
│  └──────────┘  └────────────┘  └────────────┘  └────────┬─────────┘ │
│        │              │               │                 │           │
│        │              │               │                 ▼           │
│        │              │               │          ┌──────────────────┐ │
│        │              │               └─────────→│ 05 GC 机制        │ │
│        │              │                           │ 可达性分析       │ │
│        │              │                           │ 收集器 ZGC/代   │ │
│        │              │                           └────────┬─────────┘ │
│        │              │                                    │           │
│        │              ▼                                    ▼           │
│        │         ┌──────────────────────────────────────────────┐     │
│        └────────→│ 06 JVM 调优与排查                             │     │
│                  │ OOM/CPU/FullGC排查 + 工具 (jmat/jstack)      │     │
│                  └────────────────────┬────────────────────┬────┘     │
│                                       │                    │          │
│                                       ▼                    ▼          │
│                  ┌──────────────────┐  ┌────────────────────────┐    │
│                  │ 07 JIT 执行引擎  │  │ 08 JMM 与并发          │    │
│                  │ C1/C2/分层编译   │  │ volatile/synchronized  │    │
│                  │ 内联/逃逸分析    │  │ happens-before/锁升级    │    │
│                  └──────────────────┘  └────────────────────────┘    │
│                                       │                              │
│                                       ▼                              │
│                  ┌──────────────────────────────────────────────┐   │
│                  │ 09 前沿与新特性                                │   │
│                  │ ZGC 亚毫秒停顿 / 虚拟线程 M:N / Valhalla 值类型│   │
│                  └──────────────────────────────────────────────┘   │
└───────────────────────────────────────────────────────────────────────┘

面试最高频的路径: new对象 → 堆分配 → GC回收 → 调优
面试最难回答路径: JMM 线程通信 → JIT 指令重排 → 锁升级

真题 1:"从 new 一个对象开始,讲一下 JVM 内部发生了什么"

面试公司: 阿里、字节跳动、美团(高频)

考察点: 知识串联能力。能否从类加载→内存分配→对象布局→初始化→GC 整个链路讲清楚

参考回答(PRINCIPLE CHAIN)

text
1. 类加载检查
   new Object() → 字节码指令 → 检查该类是否已加载/解析/初始化
   如未加载:启动类加载过程(双亲委派 → 加载 → 连接 → 初始化)
   → 详阅 [[02.1-类加载生命周期]]

2. 分配内存
   类加载完成后 → 确定对象所需内存大小
   指针碰撞(规整堆)或 空闲列表(不规整堆)
   优先 TLAB 分配(线程本地缓冲区,免同步)
   如 TLAB 不足 → Eden 区分配
   → 详阅 [[04.1-对象创建过程]]、[[04.4-分配策略(栈上分配与TLAB与大对象)]]

3. 内存空间清零
   分配后内存填充零值(保证字段可直接使用而不报错)
   注意:TLAB 分配时已经零值初始化

4. 设置对象头
   MarkWord: 锁状态(初始无锁)、GC 分代年龄(0)、identity hashcode(懒计算)
   KlassPointer: 指向类元数据的指针(用于方法调用时的类型确定)
   如果数组,还有数组长度
   → 详阅 [[04.2-对象内存布局(MarkWord与Klass与实例数据)]]

5. 执行 <init> 方法
   栈帧压入 → 调用父类构造 → 初始化字段 → 执行构造体
   对象引用赋值(注意:这里可能发生"对象未完全初始化"的并发问题)

6. 后续使用与 GC
   对象进入使用阶段 → 被引用 → 收集到 GC Roots 可达性分析
   通过 GC 年龄达到阈值 → 从新生代晋升到老年代
   最终被回收 → finalize() → 空间复用
   → 详阅 [[05.1-对象存活判定算法]]

追问预判:

  • : 对象分配时堆不规整,用空闲列表还是指针碰撞取决于什么?

    • : GC 收集器是否带整理功能(Serial/ParNew 带复制整理 → 指指针碰撞;CMS 标记清除 → 空间不规整 → 空闲列表)
  • : TLAB 满了之后怎么分配?

    • : 直接在 Eden 区分配(需同步)。如果 Eden 也不够 → Minor GC(Allocation Failure)。如果超过 PretenureSizeThreshold → 直接进老年代
  • : volatile 在对象创建中有什么作用?

    • : 单例 DCL 模式中,instance = new Singleton() 非原子——分配+初始化+赋值可能重排 → 其他线程拿到未初始化的对象。加 volatile 禁止重排

真题 2:"说下你对 JVM 内存模型的理解"

面试公司: 字节跳动、腾讯、快手(鉴别"是否真懂"的经典题)

考察点: 区分"运行时数据区(内存结构)"和"JMM(并发语义)"两个概念

参考回答(PRINCIPLE CHAIN)

text
首先需要澄清:JVM "内存模型"有两个含义,面试官通常希望听到两者都覆盖:

一、运行时数据区(Runtime Data Area)—— 内存的"物理"划分
   堆(Heap):所有线程共享,存对象实例,GC 管理
   虚拟机栈(Stack):线程私有,存栈帧(局部变量表、操作数栈)
   方法区(Metaspace):类信息、常量、静态变量
   程序计数器:线程私有,指向当前执行的字节码行号
   本地方法栈:native 方法

   关键:堆是所有线程共享的——这是 JMM 需要存在的原因
   → 详阅 [[03.4-Java堆]]、[[03.2-Java虚拟机栈]]、[[03.5-方法区与元空间]]

二、JMM(Java Memory Model)—— 线程间通信的契约
   解决的问题:CPU 缓存 + 指令重排导致的多线程内存操作不可见

   核心规则:happens-before 原则
   - volatile 变量的写 happens-before 读
   - synchronized 解锁 happens-before 加锁
   - Thread.start() 之前的操作 happens-before 新线程看到
   ...

   保证了三种性质:
   - 原子性(Atomicity):synchronized、CAS、锁
   - 可见性(Visibility):volatile、synchronized、final
   - 有序性(Ordering):volatile(内存屏障)、happens-before

   注意区别:运行时数据区 = 内存结构(静态的),JMM = 并发规则(动态的)
   → 详阅 [[08.1-JMM与happens-before]]

追问预判:

  • : 既然堆是共享的,为什么还有可见性问题?

    • : 线程在 CPU 上运行,从主内存(RAM)读数据到缓存(L1/L2)后再操作——不是每次都从堆读。volatile 强制从主内存读/写
  • : happens-before 和内存屏障的关系?

    • : happens-before 是 JMM 的抽象语义,内存屏障是底层实现。JVM 根据 happens-before 规则插入屏障指令,CPU 根据屏障禁止重排

真题 3:"GC 调优做过吗?怎么做的?"

面试公司: 阿里、字节跳动、京东(最高频实战题)

考察点: 是否真的经历过线上 GC 问题,还是只背参数

参考回答(PRINCIPLE CHAIN)

text
GC 调优是一个系统性的过程,不是一上来就调参数。

第一步:确定目标
   不是什么场景都需要 P99 < 10ms。先明确业务要求:
   - 批处理/离线计算 → 吞吐量优先(目标:GC 时间 < 总时间的 1-5%)
   - 在线交易/API 网关 → 延迟优先(目标:P99 GC < 10ms 甚至 < 2ms)
   - 流媒体/实时系统 → 最坏情况停顿(目标:任何 GC 都不超过 50ms)

第二步:收集现状
   jstat -gcutil pid 5000 50 → 看 Young GC 频率、Full GC 频率
   GC 日志(-Xlog:gc*)→ 每个 GC 事件的耗时和堆变化
   堆使用趋势(JVisualVM / Grafana + Prometheus + JMX Exporter)
   → 详阅 [[06.2-常用工具(jps与jstat与jmap与jstack与jinfo)]]

第三步:分析问题模式

   模式 A:Young GC 过于频繁(每秒多次)
       原因:Eden 太小 / 对象创建速率太大
       行动:调大 -Xmn 或检查是否需要减少对象创建

   模式 B:Full GC 频繁
       原因:老年代空间被占满(内存泄漏或堆太小)/ Metaspace 扩容
       行动:jmap dump → MAT 找 GC Root 链 → 修复/增大堆
       → 详阅 [[06.6-FullGC频繁排查方法论]]

   模式 C:单次 GC 停顿过长
       原因:堆太大 + G1 重定位慢 / CMS 并发失败
       行动:换 ZGC(JDK 17+)或调整 G1 的 -XX:MaxGCPauseMillis
       → 详阅 [[05.5-GC调优参数速查]]

第四步:验证与监控
   调整后观察至少一周(确保不出现偶发性 Full GC)
   关键指标:GC 频率下降、停顿减少、应用 P99 延迟降低

   生产常用配置模板:
   java -Xms8g -Xmx8g \
        -XX:+UseZGC \
        -Xlog:gc*:file=gc.log:time,level,tags:filecount=5,filesize=100m \
        -XX:+HeapDumpOnOutOfMemoryError \
        -XX:HeapDumpPath=/opt/app/dumps/

追问预判:

  • : 堆设置 8GB,老年代 6GB,YGC 一次 100ms,这个正常吗?

    • : 取决于收集器。G1 下 YGC 100ms 在 8GB 堆上偏高(通常在 20-50ms)。检查是否 Mixed GC 或巨型对象占用了大量 Region
  • : 怎么判断是"内存泄漏"还是"堆不够大"?

    • : 看 GC 后的堆用量基线。连续几次 Full GC 后堆基数不降 → 泄漏;降回基线 → 堆不够

真题 4:"CMS 和 G1 的区别?什么时候用哪个?"

面试公司: 美团、拼多多、滴滴(中高频选型题)

考察点: 对收集器本质差异的理解,以及场景判断

参考回答(PRINCIPLE CHAIN)

text
核心区别在于设计哲学:

1. 堆结构
   CMS:分代连续空间(新生代 Eden/S0/S1 + 老年代连续空间)
   G1:分区 Region(1-32MB 的独立区域,逻辑分代但物理不连续)

2. 回收算法
   CMS:新生代(标记-复制)+ 老年代(标记-清除)
   G1:新生代(标记-复制)+ 整个堆(标记-整理)

3. 关键机制
   CMS:增量更新 + 并发清除。缺陷是标记-清除产生碎片,并发失败需 STW
   G1:SATB(快照) + RSet(记录跨代引用)。可预测停顿,无碎片

4. STW 控制
   CMS:无法精确控制——停顿时间取决于堆大小
   G1:通过 -XX:MaxGCPauseMillis 设定目标(默认 200ms),选择"性价比最高"的 Region 集合

   选型建议:
   - JDK 8 老项目,堆 <8GB,低延迟:可用 CMS(但已废弃,尽快迁移)
   - JDK 9+,4-64GB 堆:G1 默认,优先选
   - >64GB 或 P50 < 5ms 要求:建议 ZGC
   → 详阅 [[05.3-垃圾收集器详解(Serial到ZGC)]]

追问预判:

  • : G1 的 RSet 会不会很大?

    • : 会。大堆 + 多跨代引用 → RSet 可能占 5-20% 堆空间。ZGC 没有 RSet(彩色指针替代),这是 ZGC 的优势之一
  • : CMS 被废弃后,为什么很多公司还在用?

    • : 大部分是因为 JDK 8 未升级 + 对迁移风险的不确定性。CMS 在 JDK 14 已正式移除

真题 5:"volatile 和 synchronized 的区别?底层实现?"

面试公司: 腾讯、京东、字节(极其高频)

考察点: JMM 深度理解 + 锁升级机制

参考回答(PRINCIPLE CHAIN)

text
一、语义层面

volatile:
   可见性(写刷主存,读从主存取)
   有序性(禁止指令重排 → 插入内存屏障)
   不保证原子性(count++ 不安全)

synchronized:
   可见性(解锁释放到主存)
   有序性(同一时刻只有一个线程执行)
   原子性(互斥执行)
   → 详阅 [[08.1-JMM与happens-before]]

二、底层实现

volatile:
   写操作:store barrier → write ← store barrier
   读操作:load barrier → read ← load barrier
   JMM 层面保证 LoadLoad/StoreStore 等屏障禁止重排

synchronized:
   字节码:monitorenter / monitorexit
   MarkWord 锁状态:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
   锁升级是单向的(降级只在 STW 的 GC 时发生)
   → 详阅 [[04.2-对象内存布局(MarkWord与Klass与实例数据)]]

三、使用选择
   单个共享变量可见性 → volatile
   复合操作原子性 + 临界区保护 → synchronized / Lock
   追求性能并且是短操作 → 偏向锁 / 轻量级锁
   高竞争 → ReentrantLock(避免重量级锁的 park/unpark 开销)

追问预判:

  • : JDK 15+ 为什么默认关闭了偏向锁?

    • : 偏向锁的撤销需要全局 STW 安全点(因为需要检查持有偏向锁的线程是否还活着)。在 JVM 内部同步(如 class 加载)中偏向锁撤销是性能热点。JDK 15+ 默认关闭偏向锁,获得性能提升
  • : volatile 数组是否保证元素可见性?

    • : 不保证。volatile int[] arr — arr 引用是 volatile,但 arr[0] 的读写不是 volatile。需要用 AtomicIntegerArray 或 Unsafe 的 loadFence/storeFence

真题 6:"说下锁升级的过程"

面试公司: 阿里、快手、拼多多(JDK 8 时代高频题,JDK 15+ 有调整)

考察点: MarkWord 的状态变化 + 每种锁的运作机制

参考回答(PRINCIPLE CHAIN)

text
Java 锁的四种状态(从无锁到重量级):

1. 无锁(No Lock)
   MarkWord 低 2 位 = 01 无锁状态
   存储:identity hashcode(懒计算)、GC 年龄

2. 偏向锁(Biased Locking)[JDK 15+ 默认关闭]
   MarkWord 存获得锁的线程 ID
   场景:单线程反复获取同一把锁(如 Vector/Hashtable)
   获取:CAS 设置线程 ID → 成功则直接进入
   撤销:遇到其他线程竞争 → 在全局安全点撤销 → 升级为轻量锁
   问题:撤销需要 STW(即使是短暂的)→ 高并发时性能反而不好

3. 轻量级锁(Lightweight Locking)
   MarkWord 指向线程栈中的 Lock Record
   获取:CAS 尝试替换 MarkWord 为指向 Lock Record 的指针
   场景:少量线程交替获取锁(不阻塞)
   失败:自旋(循环 CAS)→ 自旋一定次数仍失败 → 升级

4. 重量级锁(Heavyweight Locking / Monitor)
   MarkWord 指向 Mutex(OS 互斥量)
   获取失败 → 进入等待队列(park/unpark → OS 线程挂起/恢复)
   场景:多线程高烈度竞争
   → 详阅 [[04.2-对象内存布局(MarkWord与Klass与实例数据)]]

锁升级状态图(MarkWord 低 3 位):

   01 (无锁) ─── CAS 设 ThreadID ──→ 01 (偏向锁)
   01 (偏向锁) ── 其他线程竞争 ──→ 00 (轻量锁)
   00 (轻量锁) ── CAS 失败+自旋超时 → 10 (重量锁)
   10 (重量锁) ─────────→ 01 (无锁) 释放锁后回到无锁

追问预判:

  • : 锁能降级吗?

    • : 重量级锁到轻量级锁的降级不可用。但偏向锁到无锁的降级在 GC 的 STW 安全点中可以发生(因为可以安全的检查线程状态)
  • : Lock Record 在哪里?存什么?

    • : 在获取锁的线程的栈帧中。存两部分:Displaced MarkWord(原 MarkWord 的副本)和 owner 指针(指向 Object 的 MarkWord)

真题 7:"类的生命周期?双亲委派模型?怎么打破?"

面试公司: 阿里、华为、字节(中高频)

考察点: 类加载全链路 + 实际应用场景

参考回答(PRINCIPLE CHAIN)

text
一、类的生命周期(7 个阶段)

加载 (Loading) → 验证 (Verification) → 准备 (Preparation)
→ 解析 (Resolution) → 初始化 (Initialization) → 使用 (Using)
→ 卸载 (Unloading)

关键知识点:
   准备:静态变量赋零值(不是代码值),final 常量直接赋值
   解析:符号引用 → 直接引用(可在初始化之后发生——动态绑定)
   初始化:执行 <clinit>() 方法(按赋值语句顺序)
   → 详阅 [[02.1-类加载生命周期]]

二、双亲委派模型

   Bootstrap CL ← Extension/Platform CL ← Application CL

   工作流程:
   1. App CL 收到 loadClass 请求
   2. 询问 Extension CL 能否加载(向上委派)
   3. Extension CL 询问 Bootstrap CL
   4. Bootstrap CL 在自己的路径寻找 → 找不到 → 向下返回
   5. Extension CL 寻找 → 找不到 → 向下返回
   6. App CL 寻找 → 找到 → 返回 Class 对象
   → 详阅 [[02.2-双亲委派模型与打破]]

三、打破双亲委派

   方法:重写 loadClass()(不调 super.loadClass())

   场景:
   1. SPI(JDBC):Bootstrap CL 加载 DriverManager → 需要加载第三方驱动
      → 用 Thread Context ClassLoader(通常是 App CL)反转委派
   2. Tomcat:每个 Web 应用有自己的 WebappClassLoader
      → 优先加载 WEB-INF/lib 的类(如果找不到再委派给父加载器)
      → 实现应用间类隔离 + 不同版本的库共存
   3. OSGi:每个 Bundle 一个 ClassLoader → 类加载路径变成图(不是树)
   → 详阅 [[02.4-Tomcat与Spring类加载分析]]、[[02.5-OSGI与SPI模块化]]

追问预判:

  • : 两个 Web 应用都依赖 Spring,Tomcat 怎么处理?

    • : Spring jar 放在 Tomcat 的 shared 目录 → Shared ClassLoader 加载 → 多个 Webapp CL 共享。如果有不同版本的 Spring → 分别放在各自 WEB-INF/lib → 各自的 Webapp CL 优先加载
  • : 自己写 ClassLoader 要注意什么?

    • : 重写 findClass() 而不是 loadClass()(遵循委派)。已加载的类用 defineClass() 转化为 Class 对象。注意缓存(避免重复 defineClass)

真题 8:"你遇到过什么 OOM?怎么排查的?"

面试公司: 所有大厂(经典实战题——有没有真正处理过线上 P0 故障)

考察点: 排查方法论 + 工具熟练度 + 沟通能力

参考回答(PRINCIPLE CHAIN)

text
我遇到过一个典型案例:

【现象】订单服务每隔 2 小时出现一次 Full GC,持续 5 秒以上,导致请求超时报警

【排查过程】

Step 1: 确认 OOM 类型(不是 OOM 崩溃,而是频繁 Full GC 导致的慢响应)
   jstat -gcutil pid 2000 50
   发现 Full GC 每 15 分钟一次,每次耗时 4-7 秒
   老年代使用率在 Full GC 后只从 98% 降到 60% → 怀疑泄漏

Step 2: 获取堆快照
   jmap -dump:format=b,file=heap.hprof pid
   (如无法 jmap,在启动参数加 -XX:+HeapDumpOnOutOfMemoryError)

Step 3: MAT 分析
   Dominator Tree → 发现最大的对象是 ArrayList,持有大量 Session 对象
   GC Root Chain → SessionManager → ConcurrentHashMap → Session → 各种数据
   原因:Session 过期未被清除(配置的过期时间 8 小时 / remove 方法未被调用)
   → 详阅 [[06.4-OOM排查方法论]]

Step 4: 临时措施
   手动调用 remove 逻辑 + 重启服务恢复

Step 5: 根本解决
   Session 用 Redis 替代(避免 JVM 内存不足)
   增加健康检测:当堆 > 80% 时告警
   → 详阅 [[06.7-大厂实战案例库]]

追问预判:

  • : 如果不能用 jmap(生产环境没权限),怎么排查?

    • : 启动时预置 -XX:+HeapDumpOnOutOfMemoryError。或者用 Arthas heapdump 命令。或者启用 JMX + Prometheus 监控堆趋势
  • : OOM 有几种类型?

    • : Java heap space(堆满)、Metaspace(加载类太多)、Direct buffer memory(NIO)、unable to create new native thread(线程太多)、GC overhead limit exceeded(98% 时间花在 GC 上)

真题 9:"ZGC 了解吗?和 G1 的区别?"

面试公司: 字节、阿里、美团(JDK 21 时代的高频前沿题)

考察点: 对新特性的理解 + 场景选型

参考回答(PRINCIPLE CHAIN)

text
ZGC 是 JDK 11 引入(JDK 15 正式、JDK 21 分代正式)的全并发 GC,
核心目标:任何堆大小下 STW < 1ms。

ZGC 的核心技术:

1. 彩色指针(Colored Pointers)
   64 位指针,其中 4 位存储颜色信息
   M0/M1(交替标记位,奇数/偶数 GC 周期)、Rmtd(已重映射)、Fin(可终结)
   指针就是 metadata → 读对象状态 = 位运算(不是查表)
   没有传统 GC 的 RSet/记忆集

2. 负载屏障(Load Barrier)
   每次读对象字段时检查指针颜色
   如果颜色指示"需要处理"(如对象已移动)→ 慢速路径修正指针
   修正成本极低(一次 CAS 更新引用)

3. 全并发
   标记 / 重定位 / 重映射 全部并发
   只有根扫描(STW <0.1ms)和阶段结束(STW <0.1ms)需要暂停

与 G1 的核心对比:
   | 特性 | G1 | ZGC |
   |------|-----|------|
   | 堆结构 | Region | Region(但无分代直到 JDK 21)|
   | 标记 | 并发(SATB)| 并发(彩色指针)|
   | 重定位 | STW | 并发(负载屏障修正引用)|
   | 重映射 | 不单独做 | 懒(随下次 GC 的负载屏障自动完成)|
   | 记忆集 | RSet | 无(彩色指针替代)|
   | Pause | ~10-50ms | <1ms |
   | 堆范围 | 1MB-64GB | 8MB-16TB |
   → 详阅 [[09.1-ZGC原理]]

JDK 21 分代 ZGC:将堆分为年轻代 + 老年代(不分代时每次标记整个堆)→ 吞吐量提升 10-20%

追问预判:

  • : ZGC 的彩色指针怎么处理 4TB 以上的地址空间?

    • : ZGC 使用多重映射(Multi-Mapping),同一物理内存映射到不同的虚拟地址。不同颜色 = 不同的虚拟地址映射到同一物理页。这样修改指针颜色 = 修改虚拟地址 = 指向同一物理地址的不同视图
  • : ZGC 有什么代价?

    • : 额外的 CPU 开销(所有 load 经过 barrier),内存开销稍高(转发表),小堆(<4GB)上用 G1 更划算

真题 10:"虚拟线程是什么?什么场景用?"

面试公司: 字节、阿里、腾讯(JDK 21 正式后极高频)

考察点: 新特性理解 + 场景判断 + Pinning 问题的认知

参考回答(PRINCIPLE CHAIN)

text
虚拟线程是 Project Loom(JDK 21 正式)的核心产出,
解决了 Java 传统平台线程 1:1 映射 OS 线程的扩展性问题。

【原理】
虚拟线程是 M:N 映射。M 个虚拟线程运行在 N 个平台线程(载波)上。
   创建:~1μs(传统 ~1ms),不需要 OS 线程——栈存在堆上(~1KB,不是 ~1MB)
   挂起/恢复:用户态(Continuation:保存栈帧到堆对象),不需要 OS 上下文切换
   → 详阅 [[09.2-虚拟线程原理]]

【工作方式】
   虚拟线程运行时:
   1. 挂到 ForkJoinPool 的一个载波线程上执行
   2. 遇到阻塞(socket.read / JDBC 查询)→ JVM 挂起虚拟线程
   3. 载波线程回到池中 → 可以执行另一个虚拟线程
   4. I/O 完成后 → 虚拟线程恢复执行(可能在另一个载波上)

【适用场景】

✅ 适用:I/O 密集型
   - HTTP 服务器(Tomcat + 虚拟线程 → 每个请求一个虚拟线程)
   - 数据库查询(大量并发查询,DB driver 支持阻塞 API)
   - 消息队列消费(Kafka 消费者)
   - 网关/代理(转发请求)

❌ 不适用:
   - CPU 密集型(数学计算、视频编码、加密解密)
   - 高竞争锁(synchronized 大量争用 → pinning + 载波无法释放)
   - 纯计算并行(用 platform thread + parallel stream 更好)

【Pinning 问题】
   虚拟线程在 synchronized 块内阻塞 → 载波线程无法被释放 →
   其他虚拟线程不能用这个载波 → 并发能力下降
   解决方案:用 ReentrantLock 替换 synchronized
   → 详阅 [[09.2-虚拟线程原理]]

追问预判:

  • : 虚拟线程和 Go 的 goroutine 有什么区别?

    • : 本质相似(用户态线程)。差异:goroutine 是 Go 语言内置,与 channel 等特性深度集成;Java 虚拟线程在 JVM 层兼容现有的 Java 生态——所有现有框架(Spring、Netty、JDBC)无需大规模改动就能使用
  • : 用了虚拟线程,还需要响应式编程(WebFlux)吗?

    • : 大部分场景不再需要。WebFlux 的核心价值是"用少数线程处理大量并发 I/O"——这正是虚拟线程的强项。但 WebFlux 在背压(Backpressure)处理上仍有优势,数据流处理场景 WebFlux/RxJava 仍有价值

📚 相关链接

  • **面试高频100题** — 各分类高频题汇总
  • **大厂实战案例库** — 更多真实案例
  • **JVM文库设计文档** — 知识库设计全景

准备建议: 这 10 道题覆盖了 JVM 面试 90% 的知识点。建议每道题用自己的话完整讲一遍(录音 + 复盘) 时间分配: 每道题准备 3-5 分钟的完整回答。G1/ZGC 和虚拟线程是增量时间投入回报最高的两个方向

Knowledge4J — Java 知识库