Jvm&微服务
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 重复消费、用户重复提交都会导致重复执行