backend-java · diff

git:20260817.f3a9563 to git:20260908.a2a662c

105 added, 22 removed. Audit A to A.

---
name: backend-java
- description: Java 后端栈特有出题:JVM、并发包、Spring 机制、MySQL/Redis 在 Java 生态的考法。简历或岗位出现 Java/Spring 时加载;架构类通用题在 parent 包 backend 里。
- keywords: [java, spring, springboot, jvm, mybatis, juc, netty]
+ description: Java 后端出题:JVM 内存与 GC、并发与 JUC、Spring 生态、MyBatis/JPA、微服务组件、性能排查与线上故障。岗位或简历出现 Java/Spring/Spring Boot/Spring Cloud 后端时加载。
+ keywords: [java, spring, spring boot, spring cloud, jvm, gc, juc, mybatis, jpa, dubbo, netty, java后端, java开发, 微服务, 线程池]
layer: stack
parent: backend
---
- ## 出题原则
+ ## 岗位职责与考察重点
- - 八股必须挂场景:不问"垃圾回收有哪些算法",问"这个症状是什么 GC 问题、怎么定位"。
- - 大厂 Java 面近年重排查与原理串联,轻 API 背诵;题目要能被"背过但不懂"的人答错。
+ Java 后端是国内互联网公司体量最大的后端岗位,日常是在 Spring Boot / Spring Cloud 或 Dubbo 体系里写业务服务、接中间件、处理线上问题。真实面试里 Java 方向有一条非常稳定的主线:JVM 内存与 GC、并发(线程池、锁、JUC 工具)、Spring 原理(IoC、AOP、事务)、持久层(MyBatis/JPA 与连接池)、微服务组件(注册中心、网关、配置中心、RPC)。这些题被问烂了,所以面试官最在意的不是候选人能不能背出 CMS 和 G1 的区别,而是三件事:一是遇到 OOM、CPU 飙高、接口变慢、线程池打满时有没有真实排查过,用什么工具、看什么数据;二是对 Spring 这类"魔法"背后的机制是否真的理解,能否解释自己项目里某个注解为什么不生效;三是并发代码能不能写对,能不能指出一段代码里的竞态。
- ## 高频主题与深度阶梯(入门 → 原理 → 场景排查 → 权衡)
+ 校招侧重基础:集合源码(HashMap、ConcurrentHashMap)、synchronized 与 ReentrantLock、JVM 内存区域与垃圾回收基本流程、Spring Bean 生命周期、手写线程安全的单例或生产者消费者。社招侧重排查与设计:线上 Full GC 频繁怎么定位、线程池参数怎么定、@Transactional 失效的场景、分布式锁的坑、微服务之间调用超时链路、JDK 17/21 的虚拟线程对现有模型的影响。一线大厂近年明显减少了纯八股,改为"给你一段代码/一个现象,说问题在哪"。
- - JVM 内存与 GC:对象在堆里怎么分配 → 分代假设与 G1 Region → 频繁 Full GC / OOM 排查路径(先看什么日志、dump 怎么读)→ 吞吐 vs 停顿的收集器选型
- - 并发:synchronized 与 ReentrantLock 差异 → AQS 等待队列原理 → 线程池参数线上怎么定、队列打满会怎样 → ThreadLocal 泄漏场景
- - Spring:Bean 生命周期 → 三级缓存解决什么、解决不了什么 → @Transactional 失效的五种场景 → AOP 代理选择的代价
- - MySQL(Java 视角):InnoDB 索引结构 → 一条 IN 查询突然不走索引的排查 → 死锁日志怎么读 → 事务隔离级别与业务对映
- - Redis(Java 视角):数据结构选型 → 分布式锁的正确姿势与续期 → 大 key/热 key 定位 → Lettuce/Jedis 连接模型差异
+ ## 主题
+ ### JVM 内存结构与 OOM 排查
+ - 阶梯:堆、栈、方法区/元空间、直接内存各放什么 → 对象分配与晋升、TLAB、逃逸分析与标量替换 → 各种 OOM(heap、metaspace、direct、unable to create native thread)各自的典型原因与排查手段 → 堆大小、容器内存限制、堆外内存之间怎么分配
+ - 好题:容器内存 4G、堆设了 3G 的服务每隔几天被 OOMKilled,但 GC 日志里堆并没有满,你怀疑什么?怎么验证?
+ - 危险信号:只知道 -Xmx;分不清 JVM OOM 与容器 OOMKilled;不知道 NIO/Netty 会用直接内存
+ - 期望信号:会用 jmap/jcmd 导 heap dump 并用 MAT 看支配树;知道 MaxDirectMemorySize、元空间、线程栈、JIT 代码缓存都占容器内存;容器环境下用百分比参数(MaxRAMPercentage);能说出一次真实的内存泄漏定位(如 ThreadLocal 未清理、静态集合、连接未关)
+
+ ### GC 选型与调优
+ - 阶梯:分代假设与常见收集器(Parallel、CMS、G1、ZGC)各自的目标 → G1 的 Region、Mixed GC、停顿目标怎么工作,ZGC 的并发与染色指针 → Young GC 频繁、Full GC 频繁、单次停顿过长各自怎么定位 → 吞吐优先还是延迟优先,什么规模该上 ZGC,调参与改代码哪个先做
+ - 好题:服务每小时一次 Full GC 停顿 3 秒,你看 GC 日志时关注哪几个数字?分别指向什么原因?调大堆是好办法吗?
+ - 危险信号:背收集器名字但没看过 GC 日志;把所有问题都归为"堆太小";不知道 G1 大对象(Humongous)的问题
+ - 期望信号:会读 GC 日志中的回收前后各代大小、晋升失败、并发模式失败;知道 Full GC 常见触发(老年代满、元空间满、System.gc、晋升失败);提到用 jstat/GC 日志分析工具看趋势;先看是否有内存泄漏或大对象分配再调参
+
+ ### 线程池设计与故障
+ - 阶梯:为什么用线程池、核心参数各是什么 → 任务提交流程(核心线程→队列→最大线程→拒绝)、为什么无界队列危险 → 线程池打满、任务堆积、线程泄漏、父子任务死锁的排查 → IO 密集与 CPU 密集怎么定参数、多个线程池的隔离与监控
+ - 好题:一个服务的线程池核心 10 最大 50 队列 1000,线上出现接口超时但线程池活跃线程只有 10,为什么?改哪个参数?改了会不会引入新问题?
+ - 危险信号:用 Executors.newFixedThreadPool 而不知道是无界队列;参数按"CPU 核数 + 1"背公式却不看任务类型;不知道 ThreadLocal 在线程池里会串
+ - 期望信号:解释队列先于最大线程;给线程池命名并暴露活跃数/队列长度指标;拒绝策略按业务选(丢弃、调用者执行、降级);提到 CompletableFuture 默认用 ForkJoinPool 的坑;知道父任务等子任务且共用池会死锁
+
+ ### 锁与并发工具
+ - 阶梯:synchronized 与 ReentrantLock 的区别 → 锁升级、AQS 的队列与 state、CAS 与 ABA → 死锁怎么用 jstack 定位、锁竞争导致吞吐下降怎么优化 → 锁粒度、读写锁、无锁结构、分段与 LongAdder 的适用边界
+ - 好题:一段用 synchronized 保护 HashMap 的代码在 32 核机器上吞吐比单线程还低,怎么解释?改成 ConcurrentHashMap 后哪些操作仍然不是原子的?
+ - 危险信号:认为 volatile 能保证原子性;不知道 ConcurrentHashMap 的 size 与复合操作不是原子;从没用 jstack 看过线程状态
+ - 期望信号:能画出 AQS 的等待队列;知道 compute/merge 这类原子复合操作;锁竞争用分段、缩小临界区、读写分离解决;能解释 happens-before 与内存可见性
+
+ ### Java 内存模型与可见性
+ - 阶梯:volatile 解决什么问题 → JMM 的 happens-before 规则、指令重排、内存屏障 → 双重检查锁为什么需要 volatile、什么代码在 x86 上跑对了到 ARM 上出错 → 什么时候需要关心 JMM,final 与安全发布
+ - 好题:一个布尔标志位控制工作线程退出,线上偶发线程不退出,为什么?加 volatile 之后如果标志位换成一个计数器,还够吗?
+ - 危险信号:把 volatile 当轻量锁;不知道 JIT 会做重排与提升;说不出 happens-before 的任何一条规则
+ - 期望信号:区分原子性、可见性、有序性;知道 AtomicXxx 与 VarHandle;能解释安全发布与不可变对象的价值
+
+ ### 虚拟线程与新版本 JDK
+ - 阶梯:JDK 21 虚拟线程解决什么问题 → 与平台线程的映射、载体线程、pinning(synchronized 内阻塞)→ 上了虚拟线程后线程池、ThreadLocal、数据库连接池怎么办 → 什么业务值得迁、迁移风险与收益
+ - 好题:把一个 IO 密集的 Spring Boot 服务切到虚拟线程后吞吐没提升反而偶发卡顿,你怀疑哪些原因?
+ - 危险信号:认为虚拟线程等于协程无脑更快;不知道 pinning;不知道数据库连接池仍是瓶颈
+ - 期望信号:知道 synchronized 块内阻塞会 pin 载体线程(JDK 24 前);虚拟线程不该池化但下游资源仍需限流;ThreadLocal 在海量虚拟线程下的内存问题与 ScopedValue;提到 record、sealed、pattern matching 等语言特性的实际使用
+
+ ### Spring IoC / AOP 原理与失效场景
+ - 阶梯:IoC 与 DI 解决什么问题 → Bean 生命周期、三级缓存解决循环依赖、代理生成(JDK 动态代理 vs CGLIB)→ @Transactional / @Async / @Cacheable 不生效的原因排查 → 什么逻辑该用 AOP、什么不该,代理带来的性能与可调试性代价
+ - 好题:同一个类里方法 A 调用带 @Transactional 的方法 B,事务没生效,为什么?列出至少三种修法并说各自的问题。构造器注入的循环依赖为什么三级缓存也解决不了?
+ - 危险信号:背出三级缓存但说不清每一级存什么;不知道自调用绕过代理;把 @Transactional 放在 private 方法上
+ - 期望信号:解释代理对象与目标对象的差别;知道事务传播行为与回滚规则(默认只回滚 RuntimeException);能说出 BeanPostProcessor 的扩展点与自己用过的场景;提到 Spring Boot 自动装配的条件注解与排查方式(条件评估报告)
+
+ ### Spring Boot 与生态实践
+ - 阶梯:自动装配怎么工作 → Starter 的机制、配置优先级、Actuator 暴露了什么 → 启动慢、Bean 冲突、配置被覆盖的排查 → Spring Boot 3 / Spring 6 的变化(Jakarta、AOT、GraalVM native)值不值得迁
+ - 好题:应用启动要 90 秒,你怎么定位哪一步慢?如果是某个 Bean 初始化时同步拉远程配置,怎么改?
+ - 危险信号:不知道 spring.factories / AutoConfiguration.imports;配置来源顺序说不清;从没看过启动日志
+ - 期望信号:会用 Actuator 的 startup 端点或 ApplicationStartup 看启动耗时;懒加载与异步初始化;理解 @ConditionalOnMissingBean 与用户覆盖默认配置的机制
+
+ ### 持久层:MyBatis / JPA 与连接池
+ - 阶梯:MyBatis 与 JPA 怎么选 → MyBatis 的一二级缓存、动态 SQL、批量插入,JPA 的 N+1、懒加载与 open-in-view → 连接池(HikariCP)耗尽、慢 SQL 拖垮连接池、事务内做 RPC 的排查 → 连接池大小怎么定、读写分离在哪一层做、ORM 与手写 SQL 的边界
+ - 好题:接口偶发超时,日志显示获取数据库连接等待 30 秒,连接池大小 20,数据库 QPS 并不高,怀疑什么?怎么证明是"事务里做了远程调用"这类问题?
+ - 危险信号:连接池设成几百认为越大越好;不知道 MyBatis 一级缓存在 Spring 事务下的行为;JPA 项目全靠 findAll 后内存过滤
+ - 期望信号:连接池大小与数据库核心数、事务时长的关系;用连接池的 leak detection 或慢事务监控;大事务拆小、事务里不做 IO;批量操作用 rewriteBatchedStatements 或手写 batch;知道 JPA 的脏检查与 flush 时机
+
+ ### 微服务组件:注册发现、配置、网关、RPC
+ - 阶梯:Spring Cloud 与 Dubbo 各自的定位 → 注册中心(Nacos/Eureka/ZK)的一致性模型、心跳与摘除、配置热更新原理 → 服务下线流量仍打进来、配置推送不生效、网关成为瓶颈的排查 → 服务网格 vs SDK 模式、跨语言与统一治理的取舍
+ - 好题:发布时老实例已经关了但注册中心还没摘除,导致几秒内大量报错,你有哪些办法做到优雅下线?每种办法在哪一层生效?
+ - 危险信号:只会配置 starter 不知道原理;不知道注册中心的 AP/CP 差别;没处理过优雅停机
+ - 期望信号:先摘注册再停容器、等待在途请求、客户端缓存刷新周期;Dubbo/gRPC 的序列化、负载均衡、重试语义;网关的限流鉴权与路由热更新;配置中心的灰度与回滚
+
+ ### 分布式锁与缓存实践(Java 视角)
+ - 阶梯:为什么 synchronized 在集群里没用 → Redis 分布式锁(SET NX PX、Redisson 看门狗)与 ZK 锁的差别 → 锁过期业务没执行完、主从切换锁丢失、锁误删的处理 → 什么场景其实不需要分布式锁(幂等、乐观锁、队列串行化)
+ - 好题:用 Redis 锁保护库存扣减,某次 GC 停顿 8 秒导致锁过期后两个实例同时执行,怎么防?RedLock 值得用吗?
+ - 危险信号:del 锁不校验持有者;不知道看门狗续期;认为 RedLock 是银弹
+ - 期望信号:唯一值 + Lua 释放;fencing token 或数据库版本号做最终保护;能说出何时改用数据库乐观锁;Spring Cache 注解与 Redis 结合的过期、穿透处理
+
+ ### 性能排查工具与方法
+ - 阶梯:CPU 高怎么查、内存高怎么查 → jstack/jmap/jstat/jcmd、Arthas、async-profiler 各看什么 → 接口 P99 高但平均正常、偶发抖动、特定机器慢的定位思路 → 排查工具的线上安全性(dump 停顿、采样开销),什么该常态化监控
+ - 好题:一台机器 CPU 100%,你在两分钟内怎么定位到具体代码行?如果是 GC 线程占的 CPU 呢?如果是业务线程但每次抓到的栈都不一样呢?
+ - 危险信号:只会重启;不知道 top -H 配合 jstack 找线程;没用过 Arthas 或 profiler
+ - 期望信号:top -H → 线程 ID 转十六进制 → jstack 匹配;Arthas 的 thread/trace/watch;async-profiler 火焰图;知道 heap dump 会 STW 要挑机器与时机;结合 GC 日志与监控看趋势
+
+ ### 集合与基础源码
+ - 阶梯:HashMap 的结构与扩容 → 红黑树转换阈值、并发下的问题、ConcurrentHashMap 的分段与 CAS → 集合导致的性能与内存问题(大 Map 扩容抖动、fail-fast、内存放大)→ 什么时候需要关心这些,什么时候该用专用结构
+ - 好题:一个每秒插入百万条的去重场景,用 HashSet 内存爆了,你有哪些替代?各自的精度与内存代价?
+ - 危险信号:能背 HashMap 源码但说不出自己项目里哪里用错了集合;不知道 ArrayList 遍历删除的坑
+ - 期望信号:解释扩容与哈希冲突;预设容量;布隆过滤器、位图、外部去重;知道 String、包装类型的内存占用
+
## 好题 / 坏题对比
- - 坏:说说 HashMap 的底层原理。
- - 好:HashMap 在并发 put 时可能出什么问题?JDK8 之后为什么还是不建议并发用?换 ConcurrentHashMap 后 size() 为什么不准?
- - 坏:Spring 的 IoC 和 AOP 是什么?
- - 好:一个 @Transactional 方法内部调用同类的另一个 @Transactional 方法,事务行为是什么?为什么?怎么改?
- - 坏:Redis 有哪些数据结构?
- - 好:用 Redis 实现"点赞去重 + 统计总数",你选什么结构?千万级用户时内存怎么算?
+ - 坏:说说 JVM 的内存区域和垃圾回收算法。
+ - 好:容器限制 4G、堆设 3G 的服务每隔几天被 OOMKilled,但 GC 日志显示堆从没满过。你怀疑内存去哪了?用什么命令验证?最后怎么定参数?
+ - 坏:Spring 的事务传播行为有哪几种?
+ - 好:这段代码里同一个类的 a() 调用了带 @Transactional 的 b(),b 里抛了异常但数据没回滚。指出原因,给三种修法,并说每种修法在什么情况下又会出问题。
+
+ - 坏:线程池的七个参数是什么?
+ - 好:线程池核心 10、最大 50、队列 1000,线上接口超时但监控显示活跃线程一直是 10。解释为什么,说你会改哪个参数,以及改完之后可能引入的新问题。
+
## 项目结合钩子
- - 项目用了 Spring Boot → 问启动慢/循环依赖/配置隔离等真实遇到的问题。
- - 简历写"调优" → 必追:调了什么参数、依据什么指标、前后对比数字。
+ - 简历出现 JVM 调优 → 追调之前的 GC 指标、改了什么参数、调完的对比数据、有没有先排除内存泄漏;说不出 GC 日志内容的要标记
+ - 简历出现线程池 / 并发优化 → 追参数怎么定、监控了什么、有没有出过任务堆积或死锁
+ - 简历出现 Spring Cloud / Dubbo → 追注册中心是哪个、优雅下线怎么做、超时重试在哪一层配、服务间调用出过什么问题
+ - 简历出现 MyBatis / JPA → 追连接池大小与依据、慢 SQL 怎么发现、事务范围多大、有没有 N+1
+ - 简历出现 Redis 分布式锁 → 追锁过期怎么处理、释放怎么保证是自己的、主从切换考虑过没有
+ - 简历出现 Netty / 自研 RPC → 追线程模型、编解码与粘包、直接内存管理、空闲检测与重连
+ - 简历出现 JDK 17/21 升级 → 追迁移遇到的问题(模块化、反射限制、Jakarta 包名)、虚拟线程是否用了、收益是什么
+ - 简历出现"接口性能优化" → 追用什么工具定位(Arthas、profiler、trace)、瓶颈是什么、优化前后的 P99
- ## 期望信号提示
+ ## 出题原则
- - 好回答:机制 + 边界条件 + 排查工具名(arthas/jstat/EXPLAIN)能串起来。
- - 危险信号:只背结论不知道适用版本、把面经原文复述、说不出任何一次真实排查。
+ - 架构与方法论(缓存一致性、消息队列、限流降级)交给 backend 包,本包专注 Java 生态的实现细节与排查手段。
+ - 八股必须落到现象:任何原理题都要配一个"线上会怎么表现""你怎么验证"的追问,能背不能用的要标记。
+ - 代码级追问:并发、事务、集合相关主题优先给一段代码或一个现象让候选人找问题,而不是让他复述概念。
+ - 按 JDK 版本与项目规模出题:还在 JDK 8 的项目不追虚拟线程;小项目不追 ZGC 与容器内存分配,但必须会读 GC 日志与线程栈。