JVM & 微服务高频面试题

高频说明:题目来自 Java 后端面试常见覆盖面整理,不代表公开统计排名。
复习方式:每题先抓关键词和核心思路,再用”追问”自检能不能展开。
覆盖范围:JVM 篇 + Spring Cloud Alibaba 组件篇。

快速答题框架

  • JVM 题:先讲”是什么 / 解决什么”,再讲”机制流程”,再讲”问题与排查工具”,最后讲”实践怎么设”。
  • 微服务题:先讲”架构目标”,再落到”组件职责”,再讲”故障与治理”,最后讲”兜底实践”。

一、JVM 篇

运行时数据区域

JVM 运行时内存是怎么划分的?各区域作用?

  • 程序计数器:线程私有,记录当前执行字节码行号,唯一不会 OOM 的区域
  • 虚拟机栈:线程私有,存栈帧(局部变量表、操作数栈、动态链接、返回地址),抛 StackOverflowError / OOM
  • 本地方法栈:为 Native 方法服务,同样可 SOF / OOM
  • 堆:线程共享,存对象实例和数组,GC 主战场;分新生代(Eden + S0 + S1)与老年代
  • 方法区:线程共享,存类元信息、运行时常量池、静态变量;JDK8 改为元空间(Metaspace),用本地内存
  • 运行时常量池:方法区一部分,可动态注入(如 String.intern)

追问:哪个区域不会 OOM?→ 程序计数器

为什么 JDK8 用元空间替换永久代?

  • 永久代大小固定(-XX:MaxPermSize),易 OOM 且难调优
  • 元空间用本地内存,容量随系统内存自动扩,降低 OOM 风险
  • 也方便 HotSpot 与 JRockit 合并、统一回收策略

对象的创建过程?

  • 类加载检查 → 分配内存 → 零值初始化 → 设置对象头 → 执行 <init> 构造
  • 内存分配方式:指针碰撞(内存规整,Serial/ParNew)或空闲列表(内存碎片,CMS)
  • 线程安全:CAS + 失败重试,或优先在 TLAB(线程分配缓冲)内分配

对象的内存布局?

  • 对象头:Mark Word(哈希、GC 分代年龄、锁状态)+ 类型指针(Klass Pointer)+ 数组长度(数组才有)
  • 实例数据:字段值,按类型对齐排列
  • 对齐填充:8 字节对齐补齐

类加载机制

类加载的过程?

  • 加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载
  • 加载:通过全限定名获取二进制字节流,生成 Class 对象
  • 验证:文件格式、元数据、字节码、符号引用
  • 准备:静态变量分配内存并赋零值(final 常量在此赋初值)
  • 解析:常量池符号引用替换为直接引用
  • 初始化:执行 <clinit> 静态代码块和静态变量赋值

双亲委派模型是什么?为什么这么设计?

  • 类加载请求先委托父加载器,父加载器找不到再自己加载
  • 层级:启动类加载器(rt.jar)→ 扩展类加载器(ext)→ 应用类加载器(classpath)→ 自定义
  • 意义:避免核心类被篡改(安全)、避免重复加载、保证类型唯一性

追问:能加载自定义 java.lang.String 吗?→ 不能,启动类加载器已加载核心类

什么场景会打破双亲委派?

  • Tomcat:每个 Web 应用独立 ClassLoader 互相隔离,先自己加载再委托父
  • JDBC:Driver 接口在 rt.jar(启动类),实现类在 classpath,用线程上下文类加载器反向加载
  • SPI、OSGi、热部署同理

Java 内存模型(JMM)

JMM 是什么?解决什么问题?

  • JMM 规定线程间共享变量的可见性、原子性、有序性
  • 每个线程有工作内存(CPU 缓存抽象),主内存为共享
  • 核心问题:缓存一致性和指令重排序

volatile 的作用和原理?

  • 保证可见性:写后强制刷主内存,读前强制从主内存读
  • 禁止指令重排序:插入内存屏障
  • 不保证原子性(如 i++ 仍不安全)
  • 典型应用:DCL 单例的 instance 加 volatile,防止”半初始化对象”被暴露

追问:volatile 和 synchronized 区别?→ volatile 轻量,只保证可见性 / 有序性,不阻塞;synchronized 保证三大特性但开销大

happens-before 是什么?

  • 一组规则定义操作间的”先行发生”关系,是 JMM 对程序员的承诺
  • 如:程序顺序规则、管程锁定规则、volatile 变量规则、传递性
  • 用来判断是否存在数据竞争、是否线程安全

垃圾回收

怎么判断对象可以回收?

  • 可达性分析:从 GC Roots 出发能否到达,不可达即可回收
  • GC Roots:虚拟机栈局部变量、方法区静态属性、常量、本地方法栈 JNI 引用
  • 引用类型:强引用(不回收)、软引用(内存不足回收)、弱引用(下次 GC 回收)、虚引用(跟踪回收过程)

追问:引用计数法为什么不用?→ 循环引用无法解决

GC 算法有哪些?

  • 标记-清除:标记存活再清除,产生碎片
  • 复制:内存分两块,存活对象复制到另一块,无碎片但浪费空间(适合新生代)
  • 标记-整理:标记后存活对象向一端移动,无碎片但慢(适合老年代)
  • 分代收集:新生代用复制,老年代用标记-整理 / 清除

Minor GC / Major GC / Full GC 区别?

  • Minor GC:只回收新生代,频繁但快,Eden 满触发,Survivor 不够则晋升老年代
  • Major GC:回收老年代,常伴随 Minor
  • Full GC:回收整个堆 + 方法区,STW 时间长,应尽量避免

对象什么时候进入老年代?

  • 大对象直接进老年代(-XX:PretenureSizeThreshold)
  • 年龄到阈值(默认 15,-XX:MaxTenuringThreshold)
  • 动态年龄判断:Survivor 中相同年龄对象大小 > Survivor 空间一半,该年龄及以上进老年代
  • Minor GC 后 Survivor 放不下

常见垃圾收集器对比?

  • Serial / Serial Old:单线程,STW,客户端模式
  • ParNew:Serial 多线程版,配合 CMS
  • Parallel Scavenge + Parallel Old:吞吐量优先,JDK8 默认
  • CMS:老年代,低延迟,标记-清除,四阶段(初始标记-并发标记-重新标记-并发清除),有碎片和浮动垃圾
  • G1:整堆分 Region,可预测停顿,标记-整理 + 复制,JDK9 默认
  • ZGC:着色指针、读屏障,亚毫秒级停顿,适合大堆

CMS 和 G1 怎么选?

  • CMS:老年代收集器,标记-清除有碎片,concurrent mode failure 会退化为 Serial Old
  • G1:Region 化,可设 -XX:MaxGCPauseMillis 目标停顿,无碎片,适合大堆
  • 堆较大、要求可控停顿选 G1;老项目 JDK8 且对延迟敏感可先用 CMS

追问:G1 为什么可预测停顿?→ 按 Region 维度做回收价值优先级,回收能在目标时间内完成的最大量

什么时候触发 Full GC?怎么排查?

  • 老年代空间不足、元空间不足、显式 System.gc()、CMS 浮动垃圾来不及回收、大对象晋升失败
  • 排查:jstat -gc 看各代增长、jmap 查对象占用、看 GC 日志、MAT 分析大对象

调优与排查

常用 JVM 监控 / 排查工具有哪些?

  • jps:列 Java 进程
  • jstat:GC 统计、类加载、编译(-gc、-gcutil)
  • jmap:堆 dump、对象直方图(-histo、-dump)
  • jstack:线程栈快照,查死锁 / 线程状态
  • jconsole / jvisualvm:图形监控
  • MAT:堆 dump 离线分析
  • Arthas:在线诊断(dashboard、thread、watch、trace)

线上 OOM 怎么排查?

  • 先看错误类型:堆 OOM / 元空间 OOM / 栈 OOM / 直接内存 OOM
  • 加 -XX:+HeapDumpOnOutOfMemoryError 拿 dump
  • 用 MAT 看 dominator tree 找大对象 / 泄漏链
  • 常见原因:大集合缓存不淘汰、ThreadLocal 不 remove、连接泄漏、大查询一次加载

线上 CPU 飙高怎么排查?

  • top 找占用高的 Java 进程 PID
  • top -Hp PID 找占用高的线程 TID
  • printf “%x” TID 转十六进制
  • jstack PID | grep tid 看该线程在干什么
  • 常见:死循环、频繁 Full GC、大量计算、锁自旋等待

常用调优参数有哪些?

  • -Xms / -Xmx:堆初始 / 最大,生产建议相等避免抖动
  • -Xmn:新生代大小
  • -XX:SurvivorRatio=8:Eden : Survivor 比例
  • -XX:MaxTenuringThreshold=15:晋升年龄
  • -XX:MetaspaceSize / MaxMetaspaceSize:元空间
  • -XX:+UseG1GC:选 G1
  • -XX:MaxGCPauseMillis=200:停顿目标
  • -XX:+HeapDumpOnOutOfMemoryError + -XX:HeapDumpPath

追问:Xms 和 Xmx 为什么设一样?→ 避免堆动态扩缩带来的 GC 抖动和性能开销


二、Spring Cloud Alibaba 微服务篇

微服务基础

微服务和单体怎么取舍?

  • 单体适合早期:业务简单、团队小、边界不稳、部署链路短
  • 微服务适合:业务复杂、团队并行、模块演进节奏不同、局部扩展需求强
  • 拆分会增加网络、数据一致性、运维、监控、治理成本,不要为”显得先进”而过早拆

微服务怎么划分边界?

  • 按业务能力而非技术层拆
  • DDD:限界上下文做边界,聚合根做一致性单元
  • 数据边界:一个服务独占自己的库,不跨库 Join
  • 接口契约稳定,团队边界对齐服务边界

微服务有哪些核心治理能力?

  • 注册发现、配置中心、服务调用、负载均衡、网关、限流熔断、分布式事务、链路追踪、日志聚合、指标监控

Nacos(注册中心 + 配置中心)

Nacos 作为注册中心,是 AP 还是 CP?

  • 同时支持:临时实例走 AP(Distro 协议,心跳保活),永久实例走 CP(Raft 协议)
  • 默认临时实例走 AP,保证可用性优先

追问:为什么默认 AP?→ 微服务场景可用性优先,CP 选主期间不可用影响大

Nacos 临时实例和永久实例的区别?

  • 临时实例:客户端心跳(默认 5s)保活,下线自动剔除,适合弹性节点
  • 永久实例:服务端主动探测,不健康也不剔除,适合长驻服务
  • 健康检查方式和数据同步协议都不同

Nacos 集群怎么同步数据?

  • Distro(AP):临时实例数据,各节点对等,异步推拉 + 心跳校验
  • Raft(CP):永久实例 + 配置,强一致,选主
  • 配置变更:先写主节点 Raft 落盘,再异步推给其他节点

Nacos 保护阈值是干什么的?

  • 当健康实例比例低于阈值,不再把不健康实例从列表剔除,而是全部返回
  • 防止雪崩:避免健康实例被打垮,牺牲一致性换可用性

Nacos 配置中心怎么实现动态刷新?

  • 客户端长轮询(默认 30s)服务端,配置变化时服务端立即响应
  • @RefreshScope 注解的 Bean 会被重建,使 @Value 注入的配置生效
  • @ConfigurationProperties 的 Bean 自动绑定更新

Nacos 配置的 Data ID / Group / Namespace 怎么用?

  • Data ID:配置集,通常 ${prefix}-${profile}.${file-extension}
  • Group:配置分组,默认 DEFAULT_GROUP,可按业务 / 环境分
  • Namespace:环境隔离(dev / test / prod),默认 public
  • 优先级:精确 profile > 默认 > 共享配置 > 扩展配置

追问:配置优先级怎么排序?→ 项目内 > Nacos > 本地 application.yml


服务调用与负载均衡

OpenFeign 的工作原理?

  • 基于动态代理:@FeignClient 接口被代理,方法调用转为 HTTP 请求
  • 集成 LoadBalancer 做负载均衡,集成 Sentinel 做熔断
  • 流程:接口方法 → 动态代理 → 拼请求 → 负载均衡选实例 → HTTP 调用 → 解码响应

OpenFeign 超时和重试怎么配?

  • feign.client.config.default.connectTimeout / readTimeout
  • 重试:Ribbon 的 MaxAutoRetries;注意重试 + 幂等,非幂等接口别乱重试
  • 不同版本优先级有差异,建议显式配置

OpenFeign 日志级别有哪些?

  • NONE / BASIC(方法 URL + 状态码)/ HEADERS / FULL(含 body)
  • 需配 logging.level.xxx=debug 才会输出
  • 生产别用 FULL,body 太大影响性能

负载均衡策略有哪些?

  • 轮询(RoundRobin,默认)、随机、重试、最少连接、基于权重 / 响应时间
  • Ribbon 切换:自定义 IRule;新版本用 Spring Cloud LoadBalancer

追问:Ribbon 为什么被替代?→ 维护停止,Spring Cloud LoadBalancer 接管,默认轮询


Sentinel(限流熔断)

Sentinel 和 Hystrix 的区别?

  • Sentinel:滑动窗口统计,支持流控 / 熔断 / 系统 / 热点,规则可动态推,控制台实时监控
  • Hystrix:基于 RxJava,熔断降级为主,已停更
  • Sentinel 资源维度更细(方法 / 接口 / 参数),Hystrix 以命令做隔离

Sentinel 的限流算法有哪些?

  • 滑动窗口统计:默认,按时间窗 + Bucket 统计 QPS
  • 漏桶(匀速排队):控制请求通过速率
  • Warm Up:冷启动,预热阶段限流阈值逐步升高
  • 控制行为:直接拒绝 / Warm Up / 排队等待

Sentinel 流控规则有哪些维度?

  • 资源名 + 阈值类型(QPS / 线程数)
  • 流控模式:直接、关联(关联资源达阈值时限流自己)、链路(只限来自某调用链)
  • 流控效果:快速失败、Warm Up、排队等待

Sentinel 熔断降级的策略有哪些?

  • 慢调用比例:RT 超阈值的比例超阈值,熔断
  • 异常比例:异常数 / 请求比例超阈值
  • 异常数:异常数超阈值
  • 熔断器状态:关闭 → 打开 → 半开(放探测请求)→ 关闭 / 打开

Sentinel 规则怎么持久化?

  • 默认存内存,重启丢失
  • 持久化:推模式(写 Nacos / 配置中心,客户端监听刷新)/ 拉模式(本地文件)
  • 推荐 Nacos 数据源,规则与配置统一管理

追问:熔断和限流区别?→ 限流控制流量速率,熔断是下游故障时快速失败防止级联


Spring Cloud Gateway(网关)

Gateway 的核心概念有哪些?

  • Route:路由,由 ID + 目标 URI + Predicate + Filter 组成
  • Predicate:断言,匹配请求(Path / Method / Header / Cookie / After 等)
  • Filter:过滤器,前置 / 后置处理(鉴权、限流、日志、改写)
  • 基于 Spring WebFlux + Netty,非阻塞异步

Gateway 和 Zuul 的区别?

  • Zuul 1.x:Servlet 阻塞 IO;Zuul 2.x 异步但未广泛采用
  • Gateway:WebFlux 非阻塞,性能更好,与 Spring 生态集成更深
  • Gateway 功能更丰富,Filter 链更灵活

Gateway 怎么做动态路由?

  • 路由配置存 Nacos / 数据库,启动加载 + 监听变更
  • 自定义 RouteDefinitionRepository,结合 Nacos 配置变更刷新 RouteDefinition
  • 不重启网关即可增删路由

Gateway 怎么做统一鉴权?

  • 全局过滤器 GlobalFilter,前置换 token 校验
  • 校验 JWT:解析 + 验签 + 过期判断,失败返回 401
  • 鉴权:从 token 取用户角色,对比请求所需权限,失败返回 403
  • 粗粒度放网关,细粒度(数据 / 操作级)放服务内

追问:权限放网关还是服务里?→ 粗粒度网关做,细粒度服务内做


Seata(分布式事务)

分布式事务为什么难?

  • 跨库跨服务,本地事务无法保证多个参与方一起成功 / 失败
  • 网络不可靠:超时、重试、乱序
  • 需要权衡一致性、可用性、性能

Seata 有哪些角色?

  • TC(事务协调者):独立部署的 Seata-Server,维护全局事务状态
  • TM(事务管理器):开启 / 提交 / 回滚全局事务的发起方
  • RM(资源管理器):管理本地事务的参与方(每个微服务)

Seata 的四种事务模式?

  • AT:自动补偿,基于本地事务 + undo_log,无侵入,默认模式
  • TCC:Try-Confirm-Cancel,业务自定义三阶段,强一致但侵入大
  • SAGA:长事务编排,正传 / 补偿,适合流程长
  • XA:基于数据库 XA 协议,强一致、性能差、资源锁重

Seata AT 模式原理?

  • 一阶段:拦截 SQL,生成前镜像 → 执行 → 生成后镜像 → 存 undo_log,本地事务提交(释放本地锁)
  • 二阶段提交:异步删除 undo_log
  • 二阶段回滚:按 undo_log 后镜像反向生成补偿 SQL 恢复
  • 全局锁:分支事务提交前需获取全局锁,防止脏写

追问:AT 为什么有脏写问题?→ 本地事务已提交释放本地锁,其他事务可能改,靠全局锁兜底

Seata AT 和 TCC 怎么选?

  • AT:无侵入,适合大部分 CRUD 业务,但全局锁有性能损耗、有短暂不一致窗口
  • TCC:业务自定义,性能好、强一致,但开发成本高、要处理空回滚 / 悬挂 / 幂等
  • 资金 / 库存等强一致核心场景用 TCC,普通业务用 AT

分布式事务还有哪些方案?

  • 本地消息表:业务表 + 消息表同库事务写入,定时扫表投递 MQ
  • 事务消息(RocketMQ):半消息 + 回查,发送与消费最终一致
  • 最大努力通知:发起方尽力通知,接收方主动查询兜底
  • 这些都是最终一致性,适合异步解耦场景

服务治理与故障

什么是服务雪崩?怎么应对?

  • 雪崩:一个服务故障导致调用方资源耗尽,级联扩散到整条链路
  • 应对:超时控制、熔断降级、限流、舱壁隔离(线程池 / 信号量)、兜底返回
  • 核心:快速失败 + 资源隔离,不让故障扩散

限流、熔断、降级区别?

  • 限流:控制进入流量速率,保护自己(Sentinel QPS)
  • 熔断:下游故障 / 慢时主动断开,保护自己不被拖死,一段时间后半开试探
  • 降级:主动放弃非核心功能返回兜底,保障核心可用(返回默认值 / 缓存)
  • 三者常配合:限流挡入口、熔断断下游、降级给兜底

微服务怎么做链路追踪?

  • 一个 TraceId 贯穿整条调用链,跨服务通过请求头透传(如 W3C traceparent)
  • 采样上报:SkyWalking / Zipkin / Jaeger
  • 原理:Agent 字节码增强或 SDK 注入,异步上报到收集器
  • 配合日志聚合,按 TraceId 串联所有服务日志

微服务怎么做幂等?

  • 天然幂等:GET、DELETE、查询
  • 写操作幂等:唯一索引(防重)、token 机制、状态机、乐观锁版本号、分布式锁
  • 业务幂等:带业务幂等键写去重表

追问:为什么需要幂等?→ 网络重试、MQ 重复消费、用户重复提交都会导致重复执行