0. 阅读指南
这篇文章适合谁?
-
✅ 目标读者:准备 Java 后端面试的同学(校招 / 实习 / 社招初中级)
-
✅ 适用轮次:一面(基础与理解)/ 二面(原理+场景)/ 三面(项目与取舍)
你应该怎么用它?
① 面试前 1 天:速背路线(30~60 分钟)
按高频顺序刷:
-
Java 基础速背清单
-
集合(HashMap 必背)
-
并发(线程池+JMM 必背)
-
JVM(内存结构+GC)
-
Spring(IOC/AOP/事务失效)
-
MySQL(索引+事务隔离+锁)
-
Redis(缓存三兄弟+分布式锁)
-
MQ(可靠性+幂等)
目标:把每章的"✅背诵版"都过一遍,确保能说出口。
② 面试前 3 小时:补短板路线(20~30 分钟)
-
只看你最弱的两章:比如 JVM + MySQL
-
每题只看:✅背诵版 + 🔍追问点 + ⚠️易错点
③ 面试进行中:快速定位(像查字典)
你被问到某个点时:
-
先用 ✅背诵版讲结论(10~20 秒)
-
再用 🧠展开版补原理/过程(30~60 秒)
-
最后主动带一句 🔍追问点(表现"我知道你还想问什么")
题目标记说明
-
⭐ 高频必背(80% 会问)
-
✅ 背诵版(10~20 秒回答)
-
🧠 展开版(1~2 分钟回答)
-
🔍 追问点(面试官可能继续问)
-
⚠️ 易错点(最容易翻车的坑)
-
💡 加分点(说出来拉开差距)
-
🧪 场景题(结合项目更稳)
回答问题的"黄金结构"(万能模板)
任何技术题都按这 4 句走,基本不会乱:
-
一句话定义/结论(先让对方放心)
-
原理/机制(说明你理解而不是背)
-
对比/取舍(说明你能做工程决策)
-
落地场景/坑点(说明你打过仗)
1. 面试准备篇
这一章的目标:让你开局稳、表达清晰、抗追问,不靠"临场灵感"。
1.1 30 秒 / 60 秒自我介绍模板
✅ 30 秒(适合一面开场)
模板
-
我主要做 Java 后端开发 ,熟悉 Spring Boot / MyBatis / MySQL / Redis 等常用技术栈。
-
最近重点在 **(高频方向:并发、缓存、数据库优化、分布式治理)**上做系统学习和实践。
-
我希望应聘的岗位是 Java 后端开发,期待在 **(业务方向:电商/平台/中台/工具系统)**中负责核心功能开发与性能稳定性优化。
✅ 60 秒(适合二面/三面)
比 30 秒多两块:成果 + 能力特长
-
我主要做 Java 后端开发,技术栈以 Spring Boot 为主,配合 MyBatis、MySQL、Redis,以及常用的中间件。
-
我更擅长把需求拆成可落地的模块,关注接口设计、数据模型、异常兜底与日志可观测性。
-
在项目中我做过:(举 2 个能力标签)
-
性能:慢 SQL 排查、索引优化、缓存策略
-
稳定性:线程池治理、限流降级、幂等与重试
-
-
目前我也在系统整理面试知识体系,希望加入团队后能快速上手并承担核心模块。
🔍 追问点(面试官常问):
-
你最熟的模块是什么?
-
你做过的性能/稳定性优化有没有指标?
⚠️ 易错点:
- 不要讲经历流水账;不要背"技术名词列表"。要讲"你会用它做什么"。
1.2 项目介绍的通用模板(1 分钟讲清一个项目)
✅ 背诵版结构(推荐你每个项目都按这个说):
-
背景:这个项目解决什么业务问题?谁在用?
-
你的角色:你负责哪些模块?承担什么责任?
-
技术方案:核心架构/技术点(Spring、MySQL、Redis、MQ 等)
-
难点与取舍:你遇到的最大问题 + 你怎么解决
-
结果:上线效果/性能/稳定性/交付质量(尽量量化)
🧠 示例话术(通用版本):
-
这是一个面向(用户/业务)的系统,核心是(核心链路:下单/签发/调度/审批等)。
-
我负责(模块A、模块B),主要做接口设计、数据库表结构、缓存、异常兜底和日志追踪。
-
方案上我用(Spring Boot + MyBatis + MySQL + Redis + MQ),其中(某一块)是核心。
-
难点是(高并发/一致性/幂等/慢 SQL/链路追踪),我通过(方案)解决,并做了(监控/压测/回归)。
-
最后效果是(延迟下降/吞吐提升/错误率降低/交付周期缩短)。
1.3 常见追问题的"通用回答策略"
Q:你最擅长什么?
-
✅ 回答模板:
"我最擅长把需求落到可维护的后端实现,尤其是 接口设计 + 数据库建模 + 缓存与性能优化。如果要举例,我经常用慢 SQL 分析、索引优化和缓存策略来提升核心链路性能。"
Q:你遇到过线上问题吗?怎么排查?
-
✅ 回答模板:
"我一般按 现象→定位→验证→修复→复盘 来排查:
先看监控/日志定位接口与时间窗口,再看链路追踪,结合 DB 慢查询和线程 dump 验证原因,修复后补充告警与回归测试。"
Q:你怎么看待加班/压力?
-
✅ 回答模板:
"我能接受阶段性冲刺,但我更倾向用工程化手段减少无效加班,比如提前做监控告警、自动化测试、灰度发布和可回滚方案。"
⚠️ 易错点:
- 不要情绪化;回答要"职业化 + 工程化"。
1.4 面试表达:如何把"背的答案"说得像"懂了"
✅ 你每题都可以套用这句:
-
"先给结论:......"
-
"原理是:......(一句话)"
-
"工程上要注意:......(坑点/边界)"
-
"如果在项目里,我一般会:......(落地)"
💡 加分技巧:主动说出一个追问点
比如回答完线程池,你补一句:
"如果你继续问线程池大小怎么设置,我会按 CPU/IO 类型和压测指标来定。"
1.5 反问面试官(强烈建议准备 6 个)
✅ 一面反问(偏岗位与技术栈)
-
这个岗位负责的核心业务链路是什么?
-
项目当前最大的技术痛点是什么?性能、稳定性还是迭代效率?
-
团队现在用哪些技术栈(Spring Cloud / MQ / Redis / 监控)?
-
新人进入后 1~2 个月最主要的交付目标是什么?
✅ 二面/三面反问(偏成长与协作)
-
团队对工程质量的标准是什么?(代码规范、测试覆盖、上线流程)
-
这个岗位的成长路径通常是什么?(业务深耕/技术专项/带人)
⚠️ 易错点:
- 不要问"你们加班多吗";换成"你们如何保障交付节奏与工程质量"。
1.6 面试复盘模板(面试结束 10 分钟内写完)
复盘写得好,你每面都在进步。
A. 被问到的题目清单
-
Java:
-
并发:
-
Spring:
-
MySQL:
-
Redis:
-
场景题:
B. 我答得不好的点(原因)
- 知识点不完整 / 说不清原理 / 没有落地案例 / 表达结构乱
C. 下次改进动作(可执行)
-
把(某题)补成:结论 + 原理 + 追问
-
写 1 次模拟回答(录音 1 分钟)
-
做 1 次复盘笔记(加入题库)
2. Java 基础
2.1 面向对象与核心概念
Q1:Java 三大特性是什么?怎么理解?
-
✅ 背诵版:封装 把数据和行为包起来;继承 复用与扩展;多态同一接口不同实现,运行时决定具体方法。
-
🧠 展开版:
-
封装:private + getter/setter,隐藏实现细节,减少耦合
-
继承:is-a 关系,复用父类逻辑,但会带来强耦合
-
多态:父类引用指向子类对象,编译看左边、运行看右边
-
-
🔍 追问点:重载 vs 重写?多态的前提?接口多态怎么体现?
-
⚠️ 易错点:多态不是"变量也会变",只有方法调用是动态绑定;字段访问看引用类型。
Q2:重载(Overload)和重写(Override)区别?
-
✅ 背诵版:重载同名不同参(编译期决定);重写子类改父类方法(运行期动态绑定)。
-
🧠 展开版:
-
重载:方法名相同,参数列表不同;返回值不同不算重载
-
重写:方法签名一致;访问权限不能更严格;异常范围不能更大
-
-
🔍 追问点:
@Override有什么用?静态方法能重写吗? -
⚠️ 易错点:static 方法是隐藏(hide)不是重写。
Q3:抽象类 vs 接口怎么选?
-
✅ 背诵版:抽象类更像"模板/半成品";接口更像"能力/规范",支持多实现。
-
🧠 展开版:
-
抽象类:可以有成员变量、构造方法、普通方法;单继承
-
接口:面向能力;Java8+ 有 default/static 方法;多实现
-
-
🔍 追问点:接口 default 方法的冲突怎么解决?
-
⚠️ 易错点:接口也能有静态方法,但不是"对象调用"。
Q4:this 和 super 的区别?
-
✅ 背诵版:
this指当前对象;super访问父类成员/构造。 -
🧠 展开版:
super()必须在构造第一行;若不写默认调用父类无参构造。 -
🔍 追问点:父类没有无参构造怎么办?
-
⚠️ 易错点:父类没无参构造且你没显式
super(xxx)会编译失败。
2.2 基本数据类型与包装类
Q5:Java 基本类型有哪些?各占多少字节?
-
✅ 背诵版:byte1、short2、int4、long8、float4、double8、char2、boolean(大小与JVM实现相关,常见按1字节处理)。
-
🧠 展开版:整数默认是 int;浮点默认 double;char 是 UTF-16 code unit。
-
🔍 追问点:
int最大值多少?为什么0.1+0.2!=0.3? -
⚠️ 易错点:浮点比较不要用
==,用误差范围或 BigDecimal。
Q6:装箱/拆箱是什么?有什么坑?
-
✅ 背诵版:基本类型与包装类互转;可能触发 NPE、性能开销和
==判断陷阱。 -
🧠 展开版:
-
Integer a = 1是装箱;int b = a是拆箱 -
拆箱时 a 为 null → NPE
-
包装类缓存:
Integer默认缓存 -128~127
-
-
🔍 追问点:
Integer i1=127; i2=127; i1==i2?;128呢? -
⚠️ 易错点:比较包装类用
equals,别用==(除非你明确比较引用)。
Q7:== 和 equals() 区别?
-
✅ 背诵版:
==比引用/基本值;equals默认也比引用,通常被重写为比内容。 -
🧠 展开版:String/包装类/集合都重写了 equals,按内容判断。
-
🔍 追问点:为什么重写 equals 必须重写 hashCode?
-
⚠️ 易错点:
equals对称性、传递性要满足,否则集合行为异常。
2.3 String 与常用类(高频中的高频)
Q8:String 为什么不可变?好处是什么?
-
✅ 背诵版:String 内部字符数组不可改;好处:线程安全、可缓存(常量池)、可做 HashMap key。
-
🧠 展开版:不可变让 hashCode 可缓存,作为 key 更稳定;也能提升安全性(如 URL、文件路径)。
-
🔍 追问点:String 的 hashCode 怎么算?为什么适合当 key?
-
⚠️ 易错点:不可变不代表"没有新对象",拼接会创建新对象。
Q9:StringBuilder vs StringBuffer vs String?
-
✅ 背诵版:String 不可变;Builder 可变非线程安全更快;Buffer 线程安全但更慢。
-
🧠 展开版:循环拼接用 Builder;多线程共享才考虑 Buffer(现在更多用并发控制而不是 Buffer)。
-
🔍 追问点:
+拼接编译器会怎么优化? -
⚠️ 易错点:循环里
str = str + x会产生大量临时对象。
Q10:什么是字符串常量池?new String("a") 创建几个对象?
-
✅ 背诵版:常量池存字面量;
new String("a")至少创建 1 个堆对象,常量池没有则再放入 1 个字面量对象。 -
🧠 展开版:字面量
"a"在常量池;new一定在堆上。 -
🔍 追问点:
intern()做了什么? -
⚠️ 易错点:不同 JDK 版本对 intern 行为细节有差异,但核心是"指向池中引用"。
2.4 Object 通用方法:equals / hashCode / toString
Q11:为什么重写 equals 必须重写 hashCode?
-
✅ 背诵版:HashMap/HashSet 先用 hashCode 定桶,再用 equals 精确比较;不一致会导致"明明相等却查不到/去重失败"。
-
🧠 展开版:契约:若
a.equals(b)为 true,则a.hashCode()==b.hashCode()必须成立。 -
🔍 追问点:hashCode 相同 equals 一定相同吗?
-
⚠️ 易错点:hash 冲突是允许的,所以还要 equals 二次比较。
Q12:Comparable 和 Comparator 区别?
-
✅ 背诵版:Comparable 是类"自然顺序";Comparator 是"外部定制规则"。
-
🧠 展开版:一个类只能有一个自然顺序,但可以有多个 Comparator。
-
🔍 追问点:TreeMap/TreeSet 用哪个?
-
⚠️ 易错点:比较器实现要满足一致性,否则 TreeSet 可能丢元素。
2.5 关键字与类加载相关
Q13:final 用法和含义?
-
✅ 背诵版:final 修饰类不可继承,方法不可重写,变量不可重新赋值(引用不可变、对象可变)。
-
🧠 展开版:final 引用指向不变,但对象内部字段仍可改。
-
🔍 追问点:final 能提高性能吗?
-
⚠️ 易错点:不要误解为"对象完全不可变"。
Q14:static 的特点?
-
✅ 背诵版:static 属于类,不属于对象;类加载时初始化一次。
-
🧠 展开版:静态变量/方法共享;静态代码块在类初始化阶段执行。
-
🔍 追问点:静态内部类的使用场景?
-
⚠️ 易错点:静态方法不能直接访问实例成员(没有 this)。
Q15:类加载过程是怎样的?
-
✅ 背诵版:加载→验证→准备→解析→初始化;初始化执行
<clinit>(静态变量赋值/静态块)。 -
🧠 展开版:双亲委派保证核心类不被随意替换。
-
🔍 追问点:什么时候触发类初始化?
-
⚠️ 易错点:
Class.forName()会触发初始化;ClassLoader.loadClass()默认不初始化(看实现)。
2.6 异常体系(非常常问)
Q16:Error 和 Exception 区别?
-
✅ 背诵版:Error 多为 JVM/系统级错误(如 OOM)不建议捕获;Exception 是程序可处理异常。
-
🧠 展开版:Exception 分为 checked(必须处理)和 unchecked(RuntimeException)。
-
🔍 追问点:常见 RuntimeException 有哪些?
-
⚠️ 易错点:不要用大而全的
catch (Exception e)吞异常不处理。
Q17:finally 一定会执行吗?
-
✅ 背诵版:通常会执行;但遇到
System.exit()、JVM 崩溃、线程被强杀等可能不执行。 -
🧠 展开版:return 在 try/catch 中执行前,会先执行 finally,再返回。
-
🔍 追问点:finally 里 return 会怎样?
-
⚠️ 易错点:finally 里 return 会覆盖 try 的 return,强烈不建议。
Q18:throw 和 throws 区别?
-
✅ 背诵版:throw 抛出异常对象;throws 声明可能抛出的异常类型。
-
🧠 展开版:throws 多用于 checked 异常向上抛。
-
🔍 追问点:自定义异常怎么写?
-
⚠️ 易错点:自定义 RuntimeException 更常见(不强制捕获)。
2.7 泛型
Q19:什么是泛型?解决什么问题?
-
✅ 背诵版:泛型让类型参数化,编译期类型检查,减少强转,提升安全性。
-
🧠 展开版:核心:
List<String>在编译期检查,运行期擦除为List。 -
🔍 追问点:什么是类型擦除?有什么影响?
-
⚠️ 易错点:运行期拿不到
T的真实类型(常用反射 + Type 解决)。
Q20:? extends 和 ? super 怎么用?
-
✅ 背诵版:
extends适合"读";super适合"写"。(PECS:Producer Extends, Consumer Super) -
🧠 展开版:
List<? extends Number>不能安全 add;List<? super Integer>能 add Integer。 -
🔍 追问点:为什么 extends 不能 add?
-
⚠️ 易错点:
?不是 Object,它是"未知类型"。
2.8 反射与注解(框架题铺垫)
Q21:反射是什么?有什么优缺点?
-
✅ 背诵版:运行期获取类信息并操作;优点灵活(框架);缺点性能开销、破坏封装、安全风险。
-
🧠 展开版:Spring 的依赖注入、注解解析都基于反射。
-
🔍 追问点:反射拿 Method/Field 的流程?
-
⚠️ 易错点:
setAccessible(true)可能受安全策略限制。
Q22:注解的 RetentionPolicy 有哪些?区别?
-
✅ 背诵版:SOURCE(编译丢弃)、CLASS(进 class 但运行不可见)、RUNTIME(运行可反射读取)。
-
🧠 展开版:框架注解通常要 RUNTIME 才能生效。
-
🔍 追问点:
@Target有哪些值? -
⚠️ 易错点:Retention 选错会导致"注解读不到"。
2.9 IO / NIO
Q23:BIO / NIO 区别?
-
✅ 背诵版:BIO 阻塞、一个连接一个线程;NIO 非阻塞、Selector 轮询,一个线程管理多连接。
-
🧠 展开版:NIO 用 Channel/Buffer/Selector;适合高并发网络场景。
-
🔍 追问点:Netty 为什么快?
-
⚠️ 易错点:NIO 不等于"零阻塞",也可能有阻塞操作(例如磁盘 IO)。
Q24:什么是序列化?有哪些方式?
-
✅ 背诵版:把对象转字节用于存储/传输;Java Serializable、JSON、ProtoBuf 等。
-
🧠 展开版:Java 原生序列化可读性差、版本兼容弱、性能一般;生产常用 JSON/ProtoBuf。
-
🔍 追问点:serialVersionUID 作用?
-
⚠️ 易错点:字段变更可能反序列化失败或产生兼容问题。
2.10 Java 8+ 常考特性
Q25:Lambda 的本质是什么?
-
✅ 背诵版:Lambda 是函数式接口的实例(只有一个抽象方法的接口)。
-
🧠 展开版:常见函数式接口:Supplier/Consumer/Function/Predicate。
-
🔍 追问点:方法引用
::属于什么语法糖? -
⚠️ 易错点:接口里多个 default 方法没问题,但抽象方法只能一个。
Q26:Stream 和 for 循环怎么选?
-
✅ 背诵版:Stream 更表达式、适合链式处理;for 性能可控、调试直观。
-
🧠 展开版:并行流不一定快,取决于数据量、任务拆分成本、线程池竞争。
-
🔍 追问点:parallelStream 的坑?
-
⚠️ 易错点:并行流里不要做有副作用的共享写操作。
Q27:Optional 是为了解决什么?
-
✅ 背诵版:减少 NPE,显式表达"可能为空"。
-
🧠 展开版:链式
map/flatMap/orElseGet;但不要把 Optional 当字段类型滥用。 -
🔍 追问点:
orElsevsorElseGet? -
⚠️ 易错点:
orElse会提前执行参数表达式,可能浪费性能。
2.11 字符集与编码
Q28:UTF-8 和 UTF-16 区别?String 用的是什么?
-
✅ 背诵版:UTF-8 可变长(1~4字节);UTF-16 多数2字节,部分字符需代理对;String 内部按 UTF-16 语义处理(char 是 2 字节 code unit)。
-
🧠 展开版:emoji 等可能占两个 char(代理对)。
-
🔍 追问点:为什么
length()不等于"字符数"? -
⚠️ 易错点:按字节截断字符串会乱码。
2.12 一页速背清单
-
OOP:封装/继承/多态;重载 vs 重写
-
Object:equals/hashCode 规则
-
String:不可变、常量池、Builder/Buffer
-
包装类:缓存、拆箱 NPE、比较用 equals
-
异常:Error/Exception/Runtime;finally 特例
-
泛型:擦除;PECS(extends读,super写)
-
反射/注解:Retention=RUNTIME 才能运行读到
-
IO:BIO阻塞 vs NIO多路复用
-
编码:UTF-8/UTF-16;emoji 代理对
3. 集合(Collection / Map)
3.1 知识点速览
3.1.1 Collection vs Map
-
Collection:存"单个元素"的容器(List/Set/Queue...)
-
Map:存"键值对"的容器(HashMap/TreeMap/ConcurrentHashMap...)
3.1.2 List / Set / Map 的核心差异
| 类型 | 是否有序 | 是否可重复 | 典型实现 | 典型场景 |
|---|---|---|---|---|
| List | 有序(按插入) | 可重复 | ArrayList、LinkedList | 需要顺序、可重复数据 |
| Set | 通常无序/有序视实现 | 不可重复 | HashSet、TreeSet | 去重、集合运算 |
| Map | key 去重 | value 可重复 | HashMap、TreeMap | 通过 key 快速查 value |
3.1.3 常用集合的时间复杂度
注意:复杂度和实现&是否扩容有关,这里是典型情况
| 容器 | 查询/随机访问 | 插入/删除(尾部) | 插入/删除(中间) | 备注 |
|---|---|---|---|---|
| ArrayList | O(1) | 均摊 O(1) | O(n) | 中间插入要搬移 |
| LinkedList | O(n) | O(1)(已定位节点) | O(1)(已定位) | 定位成本高 |
| HashMap | 均摊 O(1) | 均摊 O(1) | - | 取决于 hash 分布 |
| TreeMap | O(log n) | O(log n) | - | 红黑树,天然有序 |
3.1.4 fail-fast vs fail-safe(迭代器)
-
fail-fast :迭代时结构被修改会抛
ConcurrentModificationException(ArrayList、HashMap) -
fail-safe:迭代基于"拷贝/弱一致性",不一定抛异常(CopyOnWriteArrayList、ConcurrentHashMap)
3.2 面试题库(高频)
Q1:ArrayList 和 LinkedList 区别?怎么选?
-
✅ 背诵版:ArrayList 底层数组,随机访问快 ;LinkedList 底层双向链表,插入删除(已定位)快。大多数场景优先 ArrayList。
-
🧠 展开版:
-
ArrayList:连续内存、cache 友好;扩容复制有成本
-
LinkedList:节点对象多、内存占用大;定位慢(O(n))
-
-
🔍 追问点:ArrayList 扩容机制是什么?
-
⚠️ 易错点:LinkedList 只有"已定位节点"时插入删除才快,否则先 O(n) 找位置。
Q2:ArrayList 扩容机制?为什么说"均摊 O(1)"?
-
✅ 背诵版:ArrayList 满了会扩容并复制到新数组;单次扩容成本高,但摊到多次 add 上是均摊 O(1)。
-
🧠 展开版:
-
扩容本质:申请更大数组 +
System.arraycopy -
频繁扩容会很慢 → 预估容量
new ArrayList<>(cap)是加分点
-
-
🔍 追问点:扩容会带来什么问题?(性能抖动、内存峰值)
-
⚠️ 易错点:扩容不是"每次+1",是按增长策略增长;别把扩容说成"链表追加"。
Q3:HashMap 的底层结构?put/get 过程?
-
✅ 背诵版:JDK8 HashMap = 数组 + 链表/红黑树。put:算 hash→定位桶→冲突则链表/树查找→必要时扩容;get:同样算 hash→定位桶→在链表/树中查 key。
-
🧠 展开版:
-
hash:对 key 的 hashCode 做扰动,减少冲突
-
桶下冲突:链表(少量)或红黑树(冲突多)
-
负载因子:默认 0.75,平衡空间与冲突
-
-
🔍 追问点:
-
为什么要扰动 hash?
-
什么时候链表转红黑树?
-
-
⚠️ 易错点:HashMap 查找不是"遍历全表",是先定位桶再查局部结构。
Q4:HashMap 为什么线程不安全?会出什么问题?
-
✅ 背诵版:多线程同时 put/resize 会导致数据覆盖、丢失、结构异常等。并发场景用 ConcurrentHashMap。
-
🧠 展开版:
-
竞态:两个线程同时写同一桶,后写覆盖前写
-
扩容期间:迁移过程被打断可能出现不一致
-
-
🔍 追问点:为什么 Hashtable 慢?(全表一把锁)
-
⚠️ 易错点:不要说成"HashMap 会死循环(JDK8 也会)"------更稳的说法是:并发下可能结构异常/数据丢失。
Q5:为什么重写 equals 必须重写 hashCode?和 HashMap 有什么关系?
-
✅ 背诵版:HashMap 先用 hashCode 定桶,再用 equals 精确比较;若 equals 相等但 hashCode 不等,会导致查不到/去重失败。
-
🧠 展开版:契约:
a.equals(b)==true⇒a.hashCode()==b.hashCode()必须成立。 -
🔍 追问点:hashCode 相同 equals 一定相同吗?(不一定,可能冲突)
-
⚠️ 易错点:把"hashCode 相同"当作"对象相同"是错的。
Q6:HashSet 底层是什么?为什么能去重?
-
✅ 背诵版:HashSet 底层是 HashMap,元素作为 key 存储,利用 key 的 equals/hashCode 去重。
-
🧠 展开版:value 是固定哨兵对象;去重逻辑完全复用 HashMap 的 key 规则。
-
🔍 追问点:TreeSet 底层是什么?(TreeMap/红黑树)
-
⚠️ 易错点:TreeSet 的"去重"依赖比较规则(compareTo/Comparator),而不是 equals。
Q7:TreeMap / TreeSet 适合什么场景?
-
✅ 背诵版:需要有序、范围查询、按 key 排序时用 TreeMap(O(log n))。
-
🧠 展开版:红黑树保持有序;适合 topN、区间查询、按时间/数值范围查。
-
🔍 追问点:排序规则冲突会怎样?(compare 结果为 0 会被认为"相同 key/元素")
-
⚠️ 易错点:Comparator 实现不一致会导致"元素丢失"。
Q8:ConcurrentHashMap 线程安全的核心思路(简答版)
-
✅ 背诵版:通过更细粒度的同步(CAS + 局部加锁/节点锁),读多场景性能比 Hashtable 好。
-
🧠 展开版:JDK8 的设计以桶/节点为粒度,减少全表锁竞争;读操作多数无锁。
-
🔍 追问点:为什么 fail-safe?(弱一致性迭代)
-
⚠️ 易错点:并发容器也会有竞争,只是比粗锁更好。
3.3 一页速背清单
-
List:ArrayList(快读)优先;LinkedList(已定位插删)
-
Map:HashMap(均摊 O1),TreeMap(有序 Ologn)
-
Set:HashSet=HashMap key 去重;TreeSet=排序去重
-
equals/hashCode 必须一起重写
-
并发:HashMap 不安全 → 用 ConcurrentHashMap
-
迭代:fail-fast vs fail-safe
4. 并发编程(JUC 高频面试题库)
4.1 基础概念:线程、并发与线程状态
Q1:并发(Concurrency)和并行(Parallelism)有什么区别?
-
✅ 背诵版:并发是"同一时间段交替执行"(一个 CPU 也能实现);并行是"同一时刻同时执行"(需要多核/多CPU)。
-
🧠 展开版:并发更强调处理多个任务的能力 ;并行更强调加速执行速度。
-
🔍 追问点:单核 CPU 能并行吗?(不能,只能并发)
-
⚠️ 易错点:别把"多线程"直接等同于"更快",线程切换也有成本。
Q2:Java 线程的 6 种状态是什么?
-
✅ 背诵版:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。
-
🧠 展开版:
-
RUNNABLE:包含"正在运行"和"就绪等待 CPU"
-
BLOCKED:抢不到 synchronized 的锁
-
WAITING/TIMED_WAITING:等待 notify/超时/park 等
-
-
🔍 追问点:
sleep()是什么状态?(TIMED_WAITING) -
⚠️ 易错点:
wait()会释放锁 ;sleep()不释放锁。
4.2 JMM 与 happens-before
Q3:什么是 JMM(Java 内存模型)?它解决什么问题?
-
✅ 背诵版:JMM 定义了多线程下可见性、有序性、原子性的规则,保证在不同 CPU/缓存下行为一致。
-
🧠 展开版:
-
线程都有"工作内存"(寄存器/缓存),读写可能不直接到主内存
-
导致:一个线程改了变量,另一个线程可能看不到(可见性问题)
-
指令可能重排序(有序性问题)
-
-
🔍 追问点:JMM 三大特性:原子性、可见性、有序性分别怎么保证?
-
⚠️ 易错点:JMM 是"规范",不是 JVM 的某块真实内存。
Q4:什么是 happens-before?
-
✅ 背诵版:happens-before 规定了操作之间的可见性顺序:如果 A happens-before B,那么 A 的结果对 B 可见。
-
🧠 展开版(常背 4 条就够面试):
-
程序次序规则:同线程内,前面 happens-before 后面
-
监视器锁规则:unlock happens-before 后续对同一锁的 lock
-
volatile 规则:写 volatile happens-before 后续读 volatile
-
线程启动/终止规则:start() happens-before 线程内动作;线程结束 happens-before join 返回
-
-
🔍 追问点:为什么 volatile 能保证可见性和有序性?
-
⚠️ 易错点:happens-before 不是"时间先后",是"可见性与顺序约束"。
4.3 synchronized、volatile、CAS(并发三件套)
Q5:synchronized 能解决什么问题?
-
✅ 背诵版:synchronized 通过互斥锁保证原子性 + 可见性 + 有序性(进入/退出临界区有内存语义)。
-
🧠 展开版:
-
本质:对象监视器(monitor)加锁
-
进入临界区:读取主内存最新值(可见性)
-
退出临界区:刷新写回主内存(可见性)
-
-
🔍 追问点:synchronized 修饰实例方法、静态方法、代码块锁对象分别是谁?
-
⚠️ 易错点:
-
锁对象不同 → 根本没互斥
-
锁粒度太大 → 性能差
-
Q6:volatile 关键字有什么用?能保证原子性吗?
-
✅ 背诵版:volatile 保证可见性 和禁止重排序(有序性) ,不保证原子性。
-
🧠 展开版:
-
典型场景:状态标记(stop flag)、双重校验单例(配合正确写法)
-
i++ 不是原子操作:读-改-写三步
-
-
🔍 追问点:volatile 适用场景?什么时候必须上锁?
-
⚠️ 易错点:用 volatile 修 i++ 是错的,必须用锁或原子类。
Q7:CAS 是什么?有什么问题?
-
✅ 背诵版:CAS(Compare And Swap)是"比较并交换",实现无锁原子更新;问题:ABA、自旋开销、只能保证单变量原子性。
-
🧠 展开版:
-
CAS 常见实现:AtomicInteger 的 compareAndSet
-
ABA 解决:版本号(AtomicStampedReference / AtomicMarkableReference)
-
-
🔍 追问点:什么是 ABA?举个例子?
-
⚠️ 易错点:CAS 在竞争激烈时可能更慢(大量自旋重试)。
4.4 Lock 体系与 AQS(高频核心)
Q8:ReentrantLock 和 synchronized 的区别?
-
✅ 背诵版:synchronized 是 JVM 层面,简单可靠;ReentrantLock 是 API 层面,更灵活(可中断、可定时、公平锁、多个条件队列)。
-
🧠 展开版:
-
ReentrantLock:
tryLock()、lockInterruptibly()、可选公平锁 -
Condition:可实现多个等待队列(比 wait/notify 更灵活)
-
-
🔍 追问点:什么是可重入?为什么需要可重入?
-
⚠️ 易错点:ReentrantLock 必须在 finally 里 unlock,否则死锁。
Q9:AQS 是什么?它的核心思想?
-
✅ 背诵版:AQS(AbstractQueuedSynchronizer)是 JUC 锁/同步器的基础框架:用一个 state 表示资源状态,用 FIFO 队列管理等待线程。
-
🧠 展开版:
-
独占模式:ReentrantLock
-
共享模式:Semaphore、CountDownLatch(典型)
-
通过 CAS 修改 state,失败就入队 park,唤醒再抢
-
-
🔍 追问点:CountDownLatch 和 Semaphore 的区别?
-
⚠️ 易错点:面试不要求背源码,但要说清:state + 队列 + CAS + park/unpark。
4.5 JUC 同步工具类(必备)
Q10:CountDownLatch / CyclicBarrier / Semaphore 区别?
-
✅ 背诵版:
-
CountDownLatch:倒计时门闩,一次性,等 N 个任务完成再继续
-
CyclicBarrier:栅栏,可重复使用,等 N 个线程都到齐再放行
-
Semaphore:信号量,控制并发数量(限流)
-
-
🧠 展开版:
-
Latch 常用于主线程等待多个子任务
-
Barrier 常用于多线程分阶段协作
-
Semaphore 常用于连接池/接口限流
-
-
🔍 追问点:三者底层多基于 AQS(可点到为止)
-
⚠️ 易错点:Latch 不可复用;Barrier 可复用但要注意 broken 状态。
4.6 并发容器与线程安全集合(必问)
Q11:ArrayList / HashMap 为什么线程不安全?
-
✅ 背诵版:多线程下会出现竞态条件,导致数据覆盖、结构破坏、读到脏数据等问题。
-
🧠 展开版:
-
ArrayList:扩容 + size++ 非原子
-
HashMap:并发 put 可能导致链表/树结构异常(历史上有死循环风险的经典问题)
-
-
🔍 追问点:怎么替换?(CopyOnWriteArrayList、ConcurrentHashMap、Collections.synchronizedList)
-
⚠️ 易错点:
Collections.synchronizedXxx是粗粒度锁,读多写少可能不如 COW。
Q12:ConcurrentHashMap 为什么线程安全?
-
✅ 背诵版:通过更细粒度的同步(CAS + 局部加锁)保证并发安全,比 Hashtable 粗锁更高效。
-
🧠 展开版:JDK8 主要是对桶/节点加锁,减少锁竞争;读操作大多无锁。
-
🔍 追问点:为什么 Hashtable 慢?(全表一把锁)
-
⚠️ 易错点:并发容器也不是"无敌快",写多场景照样会竞争。
4.7 线程池(面试第一大题)
Q13:为什么要用线程池?解决什么问题?
-
✅ 背诵版:复用线程,减少频繁创建销毁成本;统一管理线程数量,避免资源耗尽;提供任务队列与拒绝策略。
-
🧠 展开版:线程池主要解决:性能 + 稳定性 + 可控性。
-
🔍 追问点:线程池核心参数有哪些?
-
⚠️ 易错点:不要
newCachedThreadPool()无脑上生产,可能无限扩线程。
Q14:线程池 7 大参数是什么?(ThreadPoolExecutor)
-
✅ 背诵版:corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。
-
🧠 展开版(面试常问的执行流程):
-
任务来 → 先建到 core
-
core 满 → 进队列
-
队列满 → 扩到 max
-
max 也满 → 触发拒绝策略
-
-
🔍 追问点:队列选型有什么区别?
-
⚠️ 易错点:只会背参数不够,要能讲清"提交任务流程"。
Q15:常见阻塞队列有哪些?怎么选?
-
✅ 背诵版:
-
ArrayBlockingQueue:数组有界
-
LinkedBlockingQueue:链表(常用,有界/无界要注意)
-
SynchronousQueue:不存任务,直接交接(常用于 cached)
-
DelayQueue:延迟任务
-
-
🧠 展开版:
-
有界队列更安全(避免 OOM),需要合理拒绝策略
-
无界队列吞任务能力强,但可能堆积导致内存问题
-
-
🔍 追问点:为什么建议用有界队列?
-
⚠️ 易错点:LinkedBlockingQueue 默认可能很大(相当于无界),容易堆积。
Q16:拒绝策略有哪些?
-
✅ 背诵版:
-
AbortPolicy:直接抛异常(默认)
-
CallerRunsPolicy:调用者线程执行(削峰)
-
DiscardPolicy:直接丢弃
-
DiscardOldestPolicy:丢最老任务
-
-
🧠 展开版:生产中常用 CallerRuns 或自定义(记录日志/打点/降级)。
-
🔍 追问点:你项目里怎么处理拒绝?怎么告警?
-
⚠️ 易错点:丢任务一定要可观测(日志/监控),否则线上"静默丢单"。
Q17:线程池大小怎么设置?(面试标准答法)
-
✅ 背诵版:看任务类型:CPU 密集型接近 CPU 核数;IO 密集型可以更大(但要压测验证)。
-
🧠 展开版:
-
CPU 密集:Ncpu 或 Ncpu+1
-
IO 密集:可取 2*Ncpu 或基于等待/计算比例估算
-
最终以:CPU 利用率、队列堆积、响应时间、拒绝率为准
-
-
🔍 追问点:怎么做压测与监控?(队列长度、活跃线程、拒绝次数)
-
⚠️ 易错点:线程越多不一定越快,可能上下文切换炸裂。
4.8 ThreadLocal(必问坑点)
Q18:ThreadLocal 是什么?为什么会内存泄漏?
-
✅ 背诵版:ThreadLocal 为每个线程提供变量副本;在线程池场景如果不 remove,容易造成"脏数据/内存泄漏"。
-
🧠 展开版:
-
ThreadLocalMap 的 key 是弱引用,但 value 是强引用
-
线程长期不结束(线程池)→ value 可能残留
-
-
🔍 追问点:怎么正确使用?(try/finally remove)
-
⚠️ 易错点:ThreadLocal 不是"跨线程传递",它是"线程内隔离"。
4.9 常见并发问题与排查(面试加分项)
Q19:死锁怎么产生?怎么排查?
-
✅ 背诵版:死锁四条件:互斥、占有且等待、不可剥夺、循环等待;排查用 jstack 看线程互相等待的锁。
-
🧠 展开版:避免策略:固定加锁顺序、tryLock 超时、减少锁嵌套。
-
🔍 追问点:线上 CPU 100% 怎么查?(top → jstack / 火焰图思路)
-
⚠️ 易错点:不要用
sleep()解决并发问题,那是"碰运气"。
Q20:如何保证"只执行一次"?(经典题)
-
✅ 背诵版:用 synchronized/Lock、AtomicBoolean、或 DCL 单例(volatile + 双重校验)。
-
🧠 展开版:
-
简单可靠:synchronized
-
轻量:AtomicBoolean.compareAndSet
-
单例:DCL 必须加 volatile 防止重排序
-
-
🔍 追问点:DCL 为什么要 volatile?
-
⚠️ 易错点:不加 volatile 可能看到"半初始化对象"。
4.10 一页速背清单
-
JMM 三性:原子性/可见性/有序性
-
happens-before:锁、volatile、start/join
-
synchronized:互斥 + 内存语义
-
volatile:可见 + 禁重排,不保原子
-
CAS:无锁原子,ABA/自旋问题
-
AQS:state + 队列 + park/unpark
-
工具类:Latch(一次)、Barrier(可复用)、Semaphore(限流)
-
并发容器:CHM 替代 HashMap,COW 适合读多写少
-
线程池:参数+执行流程+队列+拒绝+大小设置
-
ThreadLocal:线程池要 remove
5. JVM
5.1 知识点速览
5.1.1 JVM 内存结构(经典:运行时数据区)
-
线程私有:程序计数器、虚拟机栈、本地方法栈
-
线程共享:堆(Heap)、方法区(JDK8+ 常叫 Metaspace/元空间)
面试常见考点:堆=对象、栈=栈帧、方法区=类元数据;以及各种 OOM 对应哪块区域。
5.1.2 对象创建与访问(大概知道即可)
-
new 对象 → 可能 TLAB 分配 → 初始化 → 引用指向对象
-
访问:引用指向堆对象(细节不深挖也能过)
5.1.3 GC 基本概念
-
为什么要 GC:自动回收不可达对象
-
怎么判断垃圾:可达性分析(GC Roots)
-
常见算法:标记-清除、复制、标记-整理
-
分代思想:新生代(复制)/老年代(整理/标记清除)
5.1.4 常见排查工具(加分项)
-
jps(看进程)/ jstack(线程栈)/ jmap(堆)/ jstat(GC 状态)
-
日志:GC log(看频繁 GC、停顿)
5.2 面试题库
Q1:JVM 运行时内存区域有哪些?各自存什么?
-
✅ 背诵版:栈存栈帧(局部变量/方法调用),堆存对象实例,方法区存类元数据;程序计数器记录执行位置。
-
🧠 展开版:
-
栈:每次方法调用创建栈帧(局部变量表、操作数栈、返回地址...)
-
堆:几乎所有对象
-
方法区/元空间:类信息、常量池、方法元数据等
-
-
🔍 追问点:哪些区域线程私有?(PC/栈/本地栈)
-
⚠️ 易错点:把"常量池"都说成在堆里不严谨------更稳:运行时常量池属于方法区逻辑范畴。
Q2:对象什么时候会被 GC 回收?怎么判断"垃圾"?
-
✅ 背诵版:不可达对象会被回收,判断用可达性分析,从 GC Roots 出发找不到的对象就是垃圾。
-
🧠 展开版:GC Roots 常见:线程栈引用、静态引用、常量引用等(点到为止)
-
🔍 追问点:引用类型有哪些?强/软/弱/虚各自用途?
-
⚠️ 易错点:不要只说"引用计数",Java 主流是可达性分析(引用计数有循环引用问题)。
Q3:强/软/弱/虚引用有什么区别?常见使用场景?
-
✅ 背诵版:
-
强引用:默认引用,不回收
-
软引用:内存紧张才回收(缓存)
-
弱引用:下一次 GC 就回收(ThreadLocal key 等)
-
虚引用:跟回收通知相关(配合 ReferenceQueue)
-
-
🧠 展开版:软引用做"可回收缓存";弱引用用于避免强持有导致泄漏。
-
🔍 追问点:ThreadLocal 为什么会"内存泄漏"?(key 弱引用、value 强引用、线程池不结束)
-
⚠️ 易错点:软引用并不等于"永不回收"。
Q4:JVM 的垃圾回收算法有哪些?
-
✅ 背诵版:标记-清除、复制、标记-整理;分代收集是组合策略。
-
🧠 展开版:
-
标记-清除:会产生碎片
-
复制:适合新生代,速度快但需要额外空间
-
标记-整理:适合老年代,减少碎片但开销更大
-
-
🔍 追问点:为什么要分代?(对象"朝生夕死")
-
⚠️ 易错点:别说"老年代用复制",典型是整理/标记清除类。
Q5:Minor GC / Full GC 有什么区别?
-
✅ 背诵版:Minor GC 回收新生代;Full GC 通常回收整个堆(含老年代)以及可能的类元数据,停顿更重。
-
🧠 展开版:Full GC 频繁通常是内存配置不合理、泄漏、晋升过快等信号。
-
🔍 追问点:什么会触发 Full GC?(老年代不足、元空间不足等)
-
⚠️ 易错点:不要把 Full GC 说成"只回收老年代",它通常更"全"。
Q6:常见垃圾收集器你了解哪些?
-
✅ 背诵版:常见有 CMS、G1;它们核心目标是降低停顿、提升吞吐,选择取决于延迟与吞吐的权衡。
-
🧠 展开版(不必背太细):
-
CMS:以低停顿为目标,但会有碎片问题
-
G1:按 Region 管理,尽量可预测停顿(更现代常用)
-
-
🔍 追问点:G1 为什么叫 Garbage First?(优先回收收益最大的 Region)
-
⚠️ 易错点:别在面试里胡报参数,只要讲清"目标/特点/适用场景"。
Q7:类加载过程是怎样的?双亲委派模型是什么?
-
✅ 背诵版:类加载:加载→验证→准备→解析→初始化;双亲委派:优先父加载器,避免核心类被篡改、保证一致性。
-
🧠 展开版:
-
启动类加载器/扩展/应用类加载器(点到为止)
-
破坏双亲委派:常见于 SPI/容器化加载(了解即可)
-
-
🔍 追问点:什么时候触发类初始化?(new、static 访问、反射等)
-
⚠️ 易错点:把"加载"和"初始化"混为一谈。
Q8:线上 OOM 常见有哪些?怎么定位?
-
✅ 背诵版:常见 OOM:堆 OOM、元空间 OOM、线程栈 OOM;定位靠日志、堆 dump、分析引用链。
-
🧠 展开版:
-
堆 OOM:对象太多/泄漏/缓存无界
-
元空间 OOM:动态生成类过多(反射/代理/脚本)
-
栈 OOM:线程开太多或递归过深
-
-
🔍 追问点:怎么拿到 dump?(OOM 时自动 dump / 手动 jmap)
-
⚠️ 易错点:只会说"加内存"是低级回答,更好:先定位增长点与引用链。
Q9:CPU 100% / 接口变慢,你会怎么排查?
-
✅ 背诵版:先定位进程与线程,再看线程栈,结合 GC/日志/慢 SQL 判断瓶颈。
-
🧠 展开版(标准排查套路):
-
top / 监控:确认是哪个进程
-
jstack:看是否死循环/死锁/大量阻塞
-
jstat / GC log:是否频繁 GC
-
DB/外部依赖:慢 SQL、超时重试
-
-
🔍 追问点:死锁怎么查?(jstack 能看到锁等待链)
-
⚠️ 易错点:别直接上来"重启",先定位原因再给修复方案+复盘措施。
Q10:JVM 调优你会怎么做?
-
✅ 背诵版:调优目标先确定(吞吐/延迟),再用数据驱动:GC 日志+监控→找问题→调整参数/代码/缓存策略→压测验证。
-
🧠 展开版:
-
先看:GC 次数、停顿、晋升、堆使用曲线
-
常见方向:堆大小、年轻代比例、避免频繁 Full GC、减少大对象
-
-
🔍 追问点:你会关注哪些指标?(P99、GC pause、吞吐、错误率)
-
⚠️ 易错点:背参数不如讲"方法论",参数要在"有证据"时才调整。
5.3 一页速背清单
-
内存:栈(栈帧)/ 堆(对象)/ 方法区(类元数据)
-
垃圾判断:可达性分析(GC Roots)
-
算法:标记清除、复制、标记整理
-
GC:Minor(新生代)/ Full(全堆更重)
-
常见故障:堆 OOM、元空间 OOM、线程栈 OOM
-
排查:jps/jstack/jmap/jstat + GC log
-
调优:先目标→看数据→再调整→压测验证
6. Spring/Spring MVC / Spring Boot
6.1 Spring 核心:IOC / DI / Bean
Q1:什么是 IOC?什么是 DI?
-
✅ 背诵版:IOC 是把对象创建和管理交给容器;DI 是容器把依赖注入到对象里(构造注入/Setter 注入等)。
-
🧠 展开版:
-
IOC:反转控制,业务不再
new对象,统一由容器维护生命周期 -
DI:依赖注入是实现 IOC 的方式(通过配置/注解把依赖"注入"进来)
-
-
🔍 追问点:你项目里常用哪种注入?(推荐构造注入)
-
⚠️ 易错点:把 IOC 和 DI 当成同一个东西说也能过,但最好区分:IOC 是思想,DI 是实现手段。
Q2:Spring Bean 的生命周期(面试必问)
-
✅ 背诵版:实例化 → 属性填充 → Aware 回调 → BeanPostProcessor 前置 → 初始化(@PostConstruct / InitializingBean)→ BPP 后置 → 使用 → 销毁(@PreDestroy / DisposableBean)。
-
🧠 展开版(能讲清 1 分钟就够):
-
创建对象(构造方法)
-
依赖注入(populate)
-
Aware:BeanNameAware、BeanFactoryAware...
-
BeanPostProcessor:前置/后置(AOP 代理就常在这里发生)
-
init:
@PostConstruct、afterPropertiesSet、自定义 init-method -
destroy:
@PreDestroy、destroy、自定义 destroy-method
-
-
🔍 追问点:AOP 代理是在生命周期哪个阶段生成的?
-
⚠️ 易错点:只背"初始化、销毁"太虚,面试官常追问:BPP 是干什么的(扩展点!)。
Q3:Bean 的作用域有哪些?默认是什么?
-
✅ 背诵版:默认 singleton ;还有 prototype、request、session、application(web 环境)。
-
🧠 展开版:
-
singleton:容器内单例(不是 JVM 单例)
-
prototype:每次获取都 new 一个,不走完整销毁回调(通常)
-
-
🔍 追问点:prototype 的销毁为什么容器不管?
-
⚠️ 易错点:singleton 是"Spring 容器维度",不是"全局唯一"。
Q4:@Autowired 注入原理?按类型还是按名称?
-
✅ 背诵版:默认按 类型 找;同类型多个时结合 @Qualifier / @Primary 决策;必要时按名称匹配。
-
🧠 展开版:
-
先按类型找候选 Bean
-
如果多个候选:@Primary 优先、@Qualifier 精确指定、或字段名/参数名匹配
-
-
🔍 追问点:@Resource 呢?(默认按名称)
-
⚠️ 易错点:多实现类不处理会直接启动失败(NoUniqueBeanDefinitionException)。
6.2 循环依赖(高频难点)
Q5:Spring 如何解决循环依赖?
-
✅ 背诵版:单例 + setter 注入 的循环依赖,Spring 通过 三级缓存(提前暴露"早期引用")解决;构造器循环依赖通常不行。
-
🧠 展开版(面试说到这就很稳):
-
一级缓存:成品 Bean
-
二级缓存:早期 Bean 引用(可能是代理)
-
三级缓存:ObjectFactory(用于生成早期引用,支持 AOP 场景)
-
-
🔍 追问点:为什么构造器循环依赖解决不了?
-
⚠️ 易错点:
-
prototype 循环依赖一般不解决
-
AOP 场景如果提前暴露不是代理可能出问题,所以需要三级缓存。
-
6.3 AOP(切面编程)
Q6:AOP 是什么?解决什么问题?
-
✅ 背诵版:AOP 把"日志、事务、权限、监控"等横切逻辑从业务中剥离,通过代理在方法前后织入,降低重复代码和耦合。
-
🧠 展开版:核心概念:切面(Aspect)、连接点(JoinPoint)、切点(Pointcut)、通知(Advice)。
-
🔍 追问点:AOP 底层怎么实现?(动态代理)
-
⚠️ 易错点:AOP 不是"只为了日志",最常见的是 事务 和 监控。
Q7:JDK 动态代理 vs CGLIB 代理区别?
-
✅ 背诵版:JDK 代理基于 接口 ;CGLIB 基于 继承生成子类,没有接口也能代理。
-
🧠 展开版:
-
JDK:更轻量,但必须有接口
-
CGLIB:可代理普通类,但 final 类/方法无法代理
-
-
🔍 追问点:Spring 默认用哪个?(有接口优先 JDK,否则 CGLIB)
-
⚠️ 易错点:final 方法不能被增强(CGLIB 也不行)。
Q8:AOP 的通知类型有哪些?
-
✅ 背诵版:@Before、@After、@AfterReturning、@AfterThrowing、@Around。
-
🧠 展开版:@Around 最强,可决定是否执行目标方法,也能统一处理耗时/异常。
-
🔍 追问点:@Around 里
proceed()不调用会怎样? -
⚠️ 易错点:Around 写错容易导致目标方法根本不执行。
6.4 事务
Q9:Spring 事务是怎么实现的?
-
✅ 背诵版:基于 AOP 代理,方法调用前开启事务,成功提交,异常回滚。
-
🧠 展开版:事务管理器(PlatformTransactionManager)+ AOP 拦截器协作完成。
-
🔍 追问点:声明式事务 vs 编程式事务?
-
⚠️ 易错点:事务边界是"代理方法调用",不是你以为的"代码块"。
Q10:事务传播行为有哪些?最常用的是哪个?
-
✅ 背诵版:默认 REQUIRED(有就加入,没有就新建);常见还有 REQUIRES_NEW、NESTED。
-
🧠 展开版:
-
REQUIRED:主流程最常用
-
REQUIRES_NEW:强制新事务(常用于"日志/审计必须落库")
-
NESTED:嵌套事务(依赖 savepoint,数据库支持相关)
-
-
🔍 追问点:REQUIRES_NEW 会挂起外层事务吗?(会)
-
⚠️ 易错点:传播行为说一堆不如把 REQUIRED / REQUIRES_NEW 讲透。
Q11:哪些异常会触发回滚?怎么自定义?
-
✅ 背诵版:默认只回滚 RuntimeException / Error ;checked 异常默认不回滚,可用
rollbackFor指定。 -
🧠 展开版:
@Transactional(rollbackFor = Exception.class)可扩大回滚范围。 -
🔍 追问点:为什么默认不回滚 checked?
-
⚠️ 易错点:很多人以为"抛异常就回滚",其实默认不是。
Q12:事务为什么会失效?
-
✅ 背诵版(记住这 5 条基本就过):
-
同类内部自调用(没走代理)
-
方法不是 public(代理可能不生效)
-
异常被 catch 吃掉 / 没抛出去
-
回滚异常类型不对(checked 未配置 rollbackFor)
-
数据源/事务管理器没配对(多数据源场景)
-
-
🧠 展开版:本质都是"没有被代理拦截到"或"回滚条件不满足"。
-
🔍 追问点:怎么解决(自调用问题)?(拆到另一个 Bean、AopContext、注入自己代理等)
-
⚠️ 易错点:最常见就是自调用,一定要会解释清楚"代理边界"。
6.5 Spring MVC(Web 请求链路必问)
Q13:Spring MVC 的执行流程?
-
✅ 背诵版:请求 → DispatcherServlet → HandlerMapping 找 Handler → HandlerAdapter 调用 Controller → 返回 ModelAndView/ResponseBody → ViewResolver 渲染或直接写回响应。
-
🧠 展开版:
-
DispatcherServlet 是前端控制器
-
HandlerInterceptor 在调用前后增强
-
@ResponseBody/ RestController 走消息转换器(HttpMessageConverter)
-
-
🔍 追问点:拦截器和过滤器区别?
-
⚠️ 易错点:过滤器是 Servlet 规范(更底层);拦截器是 Spring 体系(能拿到 Handler)。
Q14:过滤器(Filter) vs 拦截器(Interceptor) vs AOP 区别?
-
✅ 背诵版:
-
Filter:Servlet 层,URL 级别,最早进入
-
Interceptor:Spring MVC 层,基于 Handler
-
AOP:Spring 容器层,方法级别(Service 常用)
-
-
🧠 展开版:权限/鉴权常在 Filter;登录态/统一参数校验常在 Interceptor;事务/日志常在 AOP。
-
🔍 追问点:执行顺序?(Filter → Interceptor → Controller → AOP(方法))
-
⚠️ 易错点:三者不是互相替代,看"层级"和"粒度"。
Q15:@RequestBody 和 @RequestParam 区别?
-
✅ 背诵版:@RequestParam 取 query/form 参数;@RequestBody 读请求体(常用于 JSON)。
-
🧠 展开版:@RequestBody 依赖 HttpMessageConverter(Jackson 等)。
-
🔍 追问点:为什么有时候 @RequestBody 读不到?(Content-Type 不对)
-
⚠️ 易错点:GET 请求一般不建议用 @RequestBody(兼容性/规范问题)。
6.6 Spring Boot(自动配置高频)
Q16:Spring Boot 为什么能"开箱即用"?
-
✅ 背诵版:核心是 自动配置 + starter 依赖管理 + 约定优于配置。
-
🧠 展开版:
-
starter:把常用依赖"一揽子打包"
-
auto-config:根据 classpath 和配置条件加载 Bean(Conditional)
-
-
🔍 追问点:自动配置是怎么生效的?(@SpringBootApplication 组合注解)
-
⚠️ 易错点:别把 Boot 说成"替代 Spring",它是 Spring 生态的增强封装。
Q17:@SpringBootApplication 做了什么?
-
✅ 背诵版:相当于
@Configuration + @EnableAutoConfiguration + @ComponentScan。 -
🧠 展开版:扫描组件 + 开启自动配置 + 配置类能力。
-
🔍 追问点:扫描范围怎么控制?(启动类包路径、scanBasePackages)
-
⚠️ 易错点:启动类放错包,会导致组件扫不到。
Q18:配置优先级(你自己复习很实用)
-
✅ 背诵版:命令行参数 > 环境变量 > 配置文件(application.yml/properties)> 默认配置。
-
🧠 展开版:同一项配置"越靠近运行时越优先",多环境用 profile。
-
🔍 追问点:多环境怎么做?(application-dev.yml + spring.profiles.active)
-
⚠️ 易错点:同名配置多处出现要知道到底谁覆盖谁。
6.7 一页速背清单
-
IOC/DI:容器管对象,依赖注入实现
-
生命周期:实例化→注入→Aware→BPP→init→destroy
-
Scope:singleton 默认;prototype 每次 new
-
循环依赖:单例 setter + 三级缓存;构造器不行
-
AOP:代理织入;JDK(接口)/CGLIB(子类)
-
事务:默认回滚 RuntimeException;传播 REQUIRED / REQUIRES_NEW;失效=没走代理/异常吃掉
-
MVC 流程:DispatcherServlet → mapping → adapter → controller → converter/view
-
Boot:starter + auto-config + conditional;@SpringBootApplication 三合一
7. MyBatis
7.1 知识点速览
7.1.1 MyBatis 是什么?解决什么问题?
-
MyBatis 是 持久层框架 :帮你把 Java 对象 ↔ SQL ↔ 数据库结果集 的映射、参数绑定、执行、结果封装做掉。
-
核心价值:SQL 可控、学习成本低、适合复杂 SQL(对比 JPA 更灵活)。
7.1.2 关键组件
-
SqlSessionFactory / SqlSession:会话与执行入口
-
Mapper:接口代理对象(你写接口,运行时生成实现)
-
Executor:执行器(Simple/Reuse/Batch)
-
MappedStatement:一条 SQL 的"配置对象"
-
TypeHandler:Java 类型 ↔ JDBC 类型转换
-
Cache:一级/二级缓存
7.1.3 预编译与防注入
-
#{}→ PreparedStatement 预编译、安全、自动加引号 -
${}→ 字符串拼接、有 SQL 注入风险(慎用)
7.2 面试题库
Q1:MyBatis 的工作流程是怎样的?
-
✅ 背诵版:读取配置→创建 SqlSessionFactory→获取 SqlSession→获取 Mapper 代理→执行 SQL(Executor)→参数绑定→结果映射→返回对象。
-
🧠 展开版:
-
Mapper 接口本身没有实现,MyBatis 用 动态代理生成实现
-
每个 SQL 会被解析成 MappedStatement
-
执行器负责:缓存、Statement 复用、批量执行等
-
-
🔍 追问点:Mapper 为什么能直接注入并调用?(代理)
-
⚠️ 易错点:别把"SqlSession"当成线程安全对象,它通常不建议跨线程共享。
Q2:#{} 和 ${} 的区别?什么时候用?
-
✅ 背诵版:
#{}走预编译,安全;${}是直接拼接字符串,可能注入。一般都用#{},${}只在动态表名/列名等无法预编译的场景谨慎使用。 -
🧠 展开版:
-
#{}:会变成?占位符,MyBatis 负责 set 参数 -
${}:拼成最终 SQL 文本,数据库看到的就是拼接后的完整字符串
-
-
🔍 追问点:order by 动态字段怎么办?(白名单校验 +
${}) -
⚠️ 易错点:
${}一旦拼接用户输入,非常容易 SQL 注入。
Q3:MyBatis 的一级缓存和二级缓存是什么?
-
✅ 背诵版:
-
一级缓存:SqlSession 级别,默认开启
-
二级缓存:Mapper namespace 级别,需要配置开启
-
-
🧠 展开版:
-
一级缓存:同一个 SqlSession 内重复查询可能直接命中缓存
-
二级缓存:跨 SqlSession 共享,但会带来一致性问题
-
更新/提交可能会清理缓存(依配置和操作而定)
-
-
🔍 追问点:什么时候缓存不生效?(不同 SqlSession、手动清缓存、同一会话但参数不同等)
-
⚠️ 易错点:二级缓存不是"默认就安全好用",生产要谨慎评估一致性和内存占用。
Q4:MyBatis 如何防止 SQL 注入?
-
✅ 背诵版:核心是用
#{}走预编译;对必须${}的动态表/列名做 白名单校验;禁用直接拼接用户输入。 -
🧠 展开版:
-
参数一律
#{}(PreparedStatement) -
动态排序字段:只允许固定几个字段(白名单)
-
-
🔍 追问点:MyBatis 本身能完全防注入吗?(不能,
${}+ 用户输入仍会注入) -
⚠️ 易错点:把"用 MyBatis"当成"天然安全"是错的,关键是你怎么写 SQL。
Q5:动态 SQL 常用标签有哪些?
-
✅ 背诵版:
<if>、<where>、<trim>、<choose>、<foreach>、<set>。 -
🧠 展开版:
-
where/trim:避免多余 AND/OR
-
foreach:IN 查询、批量插入
-
-
🔍 追问点:foreach 批量插入怎么写?有什么限制?
-
⚠️ 易错点:where/trim 用不好会生成非法 SQL(多余逗号/AND)。
Q6:resultType 和 resultMap 区别?什么时候用 resultMap?
-
✅ 背诵版:resultType 适合字段与属性名能直接映射;resultMap 适合字段名不一致/复杂映射/多表关联/嵌套对象。
-
🧠 展开版:
- resultMap 可做:别名、字段映射、association、collection
-
🔍 追问点:一对多映射怎么做?(collection)
-
⚠️ 易错点:多表 join + resultType 容易映射错/丢字段,复杂场景用 resultMap 更稳。
Q7:MyBatis 分页怎么做?你推荐哪种?
-
✅ 背诵版:常见:SQL 级分页(limit/offset)、PageHelper 插件、MyBatis-Plus 分页;生产更推荐 SQL 级分页或成熟插件。
-
🧠 展开版:
- offset 深分页性能差(扫很多行)→ 可用"基于 id/时间游标"的方式优化
-
🔍 追问点:深分页怎么优化?(游标/延迟关联/覆盖索引)
-
⚠️ 易错点:深分页别硬上 offset=1000000,会非常慢。
Q8:MyBatis 插件(Interceptor)能做什么?
-
✅ 背诵版:插件可拦截 Executor/StatementHandler/ParameterHandler/ResultSetHandler,用于分页、审计、SQL 打印、加密脱敏等。
-
🧠 展开版:常见分页插件就是拦截 StatementHandler 改写 SQL。
-
🔍 追问点:插件会影响性能吗?(会,尤其是打印 SQL/参数)
-
⚠️ 易错点:插件过多会增加链路复杂度,注意可观测与开关。
7.3 一页速背清单
-
#{}:预编译安全;${}:拼接有注入风险 -
一级缓存:SqlSession;二级缓存:namespace
-
动态 SQL:if/where/trim/foreach/set
-
resultMap:复杂映射/多表/嵌套对象
-
分页:深分页要优化(游标)
-
插件:分页/审计/加密等(注意性能)
8. MySQL
8.1 知识点速览
8.1.1 为什么 InnoDB 是默认引擎?
- 支持 事务(ACID) 、行级锁 、MVCC、崩溃恢复能力强(redo/undo)。
8.1.2 索引核心:B+Tree & 聚簇索引
-
InnoDB 主键索引是 聚簇索引:叶子节点存整行数据
-
二级索引叶子节点存:二级索引 key + 主键值(回表靠主键)
8.1.3 事务核心:隔离级别 + MVCC + 锁
-
事务隔离级别:RU / RC / RR / Serializable
-
InnoDB 常用 RR + MVCC + Next-Key Lock 防幻读
8.1.4 慢 SQL 优化基本套路
-
看
EXPLAIN→ 看索引是否命中/扫描行数 -
重点优化:where 条件、索引设计、避免函数/隐式转换、减少回表、控制返回行数
8.2 面试题库
Q1:MySQL 常用存储引擎有哪些?InnoDB 和 MyISAM 区别?
-
✅ 背诵版:常用 InnoDB;InnoDB 支持事务/行锁/MVCC,MyISAM 不支持事务,锁粒度更粗。
-
🧠 展开版:
-
InnoDB:适合 OLTP(高并发读写)
-
MyISAM:更偏读多写少、历史场景(现在用得少)
-
-
🔍 追问点:为什么线上几乎都用 InnoDB?
-
⚠️ 易错点:别把"性能"简单归因到引擎,还是看业务与设计。
Q2:索引是什么?为什么 B+Tree 适合做索引?
-
✅ 背诵版:索引是加速查询的数据结构;B+Tree 适合范围查询、磁盘友好、树高低,查询稳定。
-
🧠 展开版:
-
B+Tree 叶子节点有序且相连,范围查询效率高
-
树高低意味着磁盘 IO 次数少
-
-
🔍 追问点:BTree vs B+Tree 区别?
-
⚠️ 易错点:别说"索引一定加速",写入会变慢(维护成本)。
Q3:什么是聚簇索引?什么是回表?怎么避免?
-
✅ 背诵版:聚簇索引(主键)叶子存整行;二级索引叶子存主键,查询整行需要再查主键(回表)。用覆盖索引可减少回表。
-
🧠 展开版:
-
覆盖索引:查询字段都在索引里(select 列被索引覆盖)
-
设计联合索引时把"常用返回列"纳入索引可减少回表(但注意索引膨胀)
-
-
🔍 追问点:
using index是什么意思?(覆盖索引) -
⚠️ 易错点:为了覆盖索引塞太多列,可能导致索引过大、写入更慢。
Q4:联合索引最左前缀原则是什么?
-
✅ 背诵版:联合索引按字段顺序建立,查询条件从最左开始连续命中才能充分利用索引(范围条件后面的列通常难以继续利用)。
-
🧠 展开版:
-
例:索引 (a,b,c)
-
where a=? ✅
-
where a=? and b=? ✅
-
where b=? ❌(缺最左)
-
where a>? and b=?:a 范围后 b 难充分利用(看优化器)
-
-
-
🔍 追问点:联合索引顺序怎么设计?(高选择性/常用过滤/排序分组需求)
-
⚠️ 易错点:把"最左前缀"理解成"只用第一列"是不对的。
Q5:EXPLAIN 你会看哪些字段?
-
✅ 背诵版:重点看 type、key、rows、Extra(Using index/Using filesort/Using temporary)。
-
🧠 展开版:
-
type:是否全表扫描(ALL 最差)
-
key:是否命中索引
-
rows:预计扫描行数
-
Extra:
-
Using index:覆盖索引
-
Using filesort:需要额外排序(可能慢)
-
Using temporary:临时表(可能慢)
-
-
-
🔍 追问点:filesort 一定慢吗?(不一定,但大数据量会慢)
-
⚠️ 易错点:只看 key 不看 rows,很容易误判(命中索引但扫描依旧大)。
Q6:事务四大特性 ACID 是什么?
-
✅ 背诵版:原子性(要么全成功要么全失败)、一致性、隔离性、持久性。
-
🧠 展开版:InnoDB 通过 redo/undo、锁、MVCC 等机制实现。
-
🔍 追问点:redo/undo 各自作用?
-
⚠️ 易错点:一致性更多是"业务+约束+事务机制"的共同结果,不只是数据库单方面。
Q7:隔离级别有哪些?分别解决什么问题?
-
✅ 背诵版:
-
RU:可能脏读
-
RC:解决脏读,可能不可重复读
-
RR:解决不可重复读,并通过锁机制避免幻读(InnoDB)
-
Serializable:最强,性能最低
-
-
🧠 展开版:企业常用 RC 或 RR(看业务取舍)。
-
🔍 追问点:什么是幻读?RR 为什么能避免幻读?
-
⚠️ 易错点:幻读和不可重复读别混:幻读强调"多了一些行"。
Q8:MVCC 是什么?解决了什么问题?
-
✅ 背诵版:MVCC 通过版本链 + Read View,让读写不互相阻塞,提高并发(主要用于一致性读)。
-
🧠 展开版:
-
一致性读:读旧版本,不加锁(或少加锁)
-
写操作通过 undo 保留旧版本
-
-
🔍 追问点:RC 和 RR 的 Read View 生成时机有什么差异?(理解层面回答即可:RC 每次读可能新视图,RR 事务内视图更稳定)
-
⚠️ 易错点:MVCC 不是"万能不加锁",写写冲突仍需要锁。
Q9:MySQL 锁有哪些?行锁、间隙锁、Next-Key Lock 是什么?
-
✅ 背诵版:InnoDB 有行锁;在 RR 下为防幻读会有间隙锁/Next-Key Lock(锁住范围)。
-
🧠 展开版:
-
行锁:锁具体记录
-
间隙锁:锁记录之间的"区间"
-
Next-Key:行锁 + 间隙锁(范围锁)
-
-
🔍 追问点:什么时候会加间隙锁?(范围查询、非唯一索引等场景常见)
-
⚠️ 易错点:锁问题经常来自"索引没用好导致锁范围变大"。
Q10:慢 SQL 常见原因有哪些?怎么优化?
-
✅ 背诵版:常见原因:索引未命中、扫描行数大、回表多、排序/分组导致临时表或 filesort、锁等待。优化:建/调索引、改 SQL、减少返回、避免深分页、拆分与缓存。
-
🧠 展开版(标准套路):
-
EXPLAIN看扫描行数/索引 -
重点优化 where 条件与索引设计(联合索引顺序)
-
避免函数、隐式转换(导致索引失效)
-
控制返回行数(limit、分页优化)
-
-
🔍 追问点:深分页怎么优化?(基于游标/延迟关联/覆盖索引)
-
⚠️ 易错点:只会说"加索引",不会说"为什么没命中索引/怎么设计联合索引"。
8.3 一页速背清单
-
InnoDB:事务/行锁/MVCC
-
索引:B+Tree;主键=聚簇索引;二级索引会回表
-
覆盖索引:减少回表(Extra: Using index)
-
联合索引:最左前缀;范围后列难继续利用
-
事务:ACID;隔离级别 RU/RC/RR/Ser
-
MVCC:版本链 + Read View,提高并发读
-
锁:行锁/间隙锁/Next-Key;索引不当会锁范围扩大
-
慢 SQL:EXPLAIN 看 type/key/rows/extra
- ⚠️ 易错点:只会说"加索引",不会说"为什么没命中索引/怎么设计联合索引"。
9. Redis
9.1 知识点速览
9.1.1 Redis 是什么?适合干什么?
-
Redis 是内存型 Key-Value 数据库,常用于:缓存、计数器、排行榜、分布式锁、会话、消息队列(轻量)、限流、延迟任务(视方案)。
-
核心优势:读写快、数据结构丰富、生态成熟。
9.1.2 为什么 Redis 快?
-
内存读写 + 高效数据结构
-
单线程(传统命令执行模型)避免复杂锁竞争(同时也有 IO/多线程等辅助机制,不影响你面试的核心解释)
-
网络模型高效、命令简单、序列化开销可控
9.1.3 Redis 常用数据结构 & 场景
| 结构 | 典型命令 | 场景 |
|---|---|---|
| String | get/set/incr | 缓存、计数器、分布式锁 |
| Hash | hget/hset | 对象缓存(用户信息) |
| List | lpush/rpop | 消息队列(轻量)、时间线 |
| Set | sadd/smembers | 去重、共同好友 |
| ZSet | zadd/zrange | 排行榜、延迟队列(按 score) |
面试加分:提一句"Redis 底层会根据数据量选择不同编码(如 ziplist/hashtable 等)",点到为止即可。
9.1.4 持久化与高可用
-
持久化:RDB(快照)/ AOF(追加日志)/ 混合(可选)
-
高可用:主从复制、哨兵、集群(Cluster)
-
核心工程目标:不丢数据、能恢复、能扩容、能故障转移
9.2 面试题库
Q1:Redis 和 MySQL 缓存怎么配合?
-
✅ 背诵版:Redis 做热点数据缓存,MySQL 做最终存储;关键是缓存一致性、过期策略和兜底降级。
-
🧠 展开版:
-
读:先查缓存,miss 再查 DB,回填缓存
-
写:要考虑一致性(见后面双写策略)
-
兜底:缓存不可用时要降级/限流,避免 DB 被打爆
-
-
🔍 追问点:缓存一致性怎么保证?(见 Q6)
-
⚠️ 易错点:只说"加缓存就快了"不够,要说"怎么防穿透/击穿/雪崩"。
Q2:缓存穿透 / 击穿 / 雪崩分别是什么?怎么解决?
-
✅ 背诵版:
-
穿透:查不存在的数据 → 每次都打到 DB
-
击穿:某个热点 key 过期 → 大量请求同时打 DB
-
雪崩:大量 key 同时过期/Redis 故障 → DB 被打爆
-
-
🧠 展开版(标准解法):
-
穿透:缓存空值、布隆过滤器、参数校验
-
击穿:互斥锁(单飞)、逻辑过期、热点 key 永不过期+异步刷新
-
雪崩:过期时间加随机、分批过期、多级缓存、限流降级、主从/哨兵保障
-
-
🔍 追问点:布隆过滤器会误判吗?(会,有误判但无漏判)
-
⚠️ 易错点:只会背概念不会落地;要能说"你会选哪种方案 + 为什么"。
Q3:Redis 的过期策略和淘汰策略是什么?
-
✅ 背诵版:过期键删除包括定期删除 + 惰性删除;内存不足会按淘汰策略(如 LRU/LFU/随机/只淘汰过期等)移除数据。
-
🧠 展开版:
-
惰性删除:访问时发现过期才删
-
定期删除:周期扫描一部分 key
-
淘汰策略:看配置(常见 allkeys-lru / allkeys-lfu / volatile-lru 等)
-
-
🔍 追问点:LRU 和 LFU 区别?(最近使用 vs 使用频率)
-
⚠️ 易错点:过期策略≠淘汰策略;淘汰只在内存不足触发。
Q4:RDB 和 AOF 区别?怎么选?
-
✅ 背诵版:RDB 是快照,恢复快但可能丢最近数据;AOF 记录写命令,数据更完整但文件更大,恢复更慢。生产常结合使用。
-
🧠 展开版:
-
RDB:适合做全量备份、重启恢复快
-
AOF:追加写更安全,可重写压缩
-
选择:看业务对"丢数据容忍度"和"恢复速度"的权衡
-
-
🔍 追问点:AOF 重写是干什么?(压缩/合并命令,减少文件体积)
-
⚠️ 易错点:AOF 也不是"完全不丢",仍取决于刷盘策略与宕机时机。
Q5:Redis 主从复制、哨兵、集群分别解决什么?
-
✅ 背诵版:
-
主从:读写分离、冗余
-
哨兵:自动故障转移(选主)
-
集群:分片扩容(水平扩展)+ 高可用
-
-
🧠 展开版:
-
主从:主写从读;复制延迟要关注
-
哨兵:监控主从,主挂了自动切主
-
集群:slot 分片,key 分布到不同节点
-
-
🔍 追问点:主从延迟带来什么问题?(读到旧数据)
-
⚠️ 易错点:读写分离不是银弹,延迟一致性要说明白。
Q6:缓存一致性怎么做?(双写一致性高频)
-
✅ 背诵版:常见做法是 先更新 DB,再删除缓存(或延迟双删);读写都要考虑并发与重试,必要时引入消息队列/订阅通知保证最终一致。
-
🧠 展开版(面试稳答法):
-
推荐:写 DB → 删除缓存(让下一次读从 DB 回填新值)
-
延迟双删:删除缓存 → 写 DB → 延迟再删一次(处理并发读回填旧值)
-
高一致场景:用 MQ/CDC 订阅变更来驱动缓存更新(最终一致)
-
-
🔍 追问点:为什么不是"更新缓存"?(并发下容易出现覆盖、顺序问题,复杂度更高)
-
⚠️ 易错点:只说"删缓存"不提失败重试/补偿,面试官会追问可靠性。
Q7:Redis 分布式锁怎么实现?有什么坑?
-
✅ 背诵版:用
SET key value NX PX ttl加锁,value 用唯一标识;释放锁用 Lua 脚本校验 value 再删,避免误删别人的锁。 -
🧠 展开版:
-
加锁:SET NX + 过期时间(避免死锁)
-
解锁:必须"校验持有者"再删除(Lua 原子性)
-
续期:看业务是否需要(长任务)
-
-
🔍 追问点:锁过期但任务没完成怎么办?(续期/看门狗/合理 TTL)
-
⚠️ 易错点:
-
用
setnx后再expire分两步是坑(非原子) -
不校验 value 直接 del 会误删别人的锁
-
备注:关于 RedLock 是否"强一致"在业界存在讨论。面试里建议说:单 Redis 实例锁适合大多数工程场景;强一致分布式锁要结合业务与更强协调机制评估。
Q8:什么是大 key / 热 key?怎么治理?
-
✅ 背诵版:大 key 是单个 value 过大导致阻塞与内存压力;热 key 是访问过于集中导致单点压力。治理:拆分、分片、异步、限流、预热、本地缓存。
-
🧠 展开版:
-
大 key:hash/list/set 元素过多或大字符串;操作会阻塞主线程
-
热 key:集中在一个 key 上(比如秒杀库存)
-
治理:拆 key、分桶(key 加后缀)、读写分离、本地缓存、热点永不过期+异步刷新
-
-
🔍 追问点:如何发现?(监控 QPS、慢查询、bigkey 工具/采样)
-
⚠️ 易错点:热 key 不能只靠扩容解决,需要"打散访问"。
9.3 一页速背清单
-
数据结构:String/Hash/List/Set/ZSet + 场景
-
三大问题:穿透/击穿/雪崩 + 对应解法
-
一致性:写 DB → 删缓存(延迟双删/订阅驱动)
-
持久化:RDB 快照、AOF 更完整、组合更稳
-
高可用:主从/哨兵/集群(分片)
-
分布式锁:SET NX PX + Lua 校验释放
-
治理:大 key / 热 key 拆分与限流
10. MQ(RabbitMQ)
10.1 知识点速览
10.1.1 为什么要用 MQ?
-
削峰填谷:扛住瞬时流量
-
异步解耦:下游慢/不稳定不拖垮主链路
-
广播通知:多系统订阅同一事件
面试加分:顺带说一句代价:引入最终一致、消息丢失/重复/积压的治理成本。
10.1.2 RabbitMQ 的核心组件(一定要会)
-
Producer:生产者
-
Exchange:交换机(路由规则)
-
Queue:队列(存消息)
-
Binding:绑定(exchange → queue 的规则)
-
RoutingKey:路由键
-
Consumer:消费者
10.1.3 常见交换机类型(面试高频)
-
Direct:精确匹配 routingKey
-
Topic:通配符匹配(最常用)
-
Fanout:广播
-
Headers:按 header 匹配(少用)
10.2 面试题库
Q1:RabbitMQ 的消息流转过程?
-
✅ 背诵版:Producer 发消息到 Exchange → 按 routingKey/binding 路由到 Queue → Consumer 从 Queue 拉取/推送消费并 ack。
-
🧠 展开版:Exchange 不存消息,只负责路由;Queue 才存消息。
-
🔍 追问点:如果路由不到任何队列怎么办?(mandatory、备份交换机等机制)
-
⚠️ 易错点:把 Exchange 当成"队列"说会直接扣分。
Q2:MQ 如何保证消息"不丢"?(生产者/队列/消费者三段都要答)
-
✅ 背诵版:生产者用 confirm;队列端消息持久化;消费者手动 ack + 失败重试/死信队列。
-
🧠 展开版(标准三段式):
-
生产者不丢:Publisher Confirm(确认机制),失败重发/记录
-
Broker 不丢:Exchange/Queue durable、消息 persistent、落盘策略
-
消费者不丢:手动 ack;消费失败重试(限次数)→ 入 DLQ(死信队列)
-
-
🔍 追问点:confirm 和事务机制有什么区别?(confirm 更常用、性能更好)
-
⚠️ 易错点:只说"消息持久化"不够,生产者没 confirm 一样可能丢。
Q3:如何处理"重复消费"?怎么做到幂等?
-
✅ 背诵版:MQ 语义通常是"至少一次",重复消费靠幂等解决:业务唯一键去重、幂等表/状态机、去重缓存、分布式锁等。
-
🧠 展开版(常用方案):
-
唯一业务 ID(orderId、requestId)+ 幂等表:已处理直接返回
-
Redis 去重:SETNX 记录已消费(注意过期与补偿)
-
数据库唯一约束:插入冲突即认为已处理
-
-
🔍 追问点:幂等表怎么设计?(message_id + status + processed_time)
-
⚠️ 易错点:别说"MQ 保证 exactly-once"------多数情况下做不到,只能"至少一次 + 幂等"。
Q4:如何保证"顺序消费"?
-
✅ 背诵版:顺序需要"同一业务 key 的消息进入同一队列",并且队列内通常单线程/单消费者按序处理。
-
🧠 展开版:
-
单队列单消费者:最简单,但吞吐受限
-
多队列分片:按 orderId hash 到固定队列,实现"分区内有序"
-
-
🔍 追问点:并发消费者会怎样?(可能乱序)
-
⚠️ 易错点:RabbitMQ 默认不保证跨队列顺序;顺序是"设计出来的"。
Q5:消息积压怎么办?怎么治理?
-
✅ 背诵版:先定位原因(生产太快/消费太慢/下游慢),再扩容消费者、限流、优化处理、必要时转移堆积消息。
-
🧠 展开版(治理套路):
-
监控队列深度、消费 TPS、处理耗时、失败率
-
提升消费能力:增加消费者实例、优化逻辑、批量处理
-
限流削峰:生产侧限流/降级、消费侧 prefetch 控制
-
兜底:死信队列/隔离队列,避免拖垮主链路
-
-
🔍 追问点:prefetch 是什么?(控制一次推送给消费者的未 ack 消息数量)
-
⚠️ 易错点:盲目加消费者可能压垮 DB/下游(要联动下游容量)。
Q6:死信队列(DLQ)是什么?什么时候会进入?
-
✅ 背诵版:消息无法正常投递或消费失败后进入 DLQ;常见原因:拒绝(reject/nack)、过期 TTL、队列满等。
-
🧠 展开版:
-
消费失败:nack 并设置 requeue=false → DLQ
-
TTL 过期:进入 DLQ 做补偿/人工处理
-
-
🔍 追问点:DLQ 常用来做什么?(失败隔离、延迟重试、人工补偿)
-
⚠️ 易错点:不要让 DLQ 变成"垃圾桶",需要配套告警与处理流程。
Q7:RabbitMQ 的推/拉模型与 ACK 机制?
-
✅ 背诵版:RabbitMQ 多为推送给消费者;消费者处理成功后 ack,失败可 nack/reject 并决定是否 requeue。
-
🧠 展开版:
-
自动 ack:吞吐高但风险大(崩溃会丢)
-
手动 ack:可靠但要注意积压和超时
-
-
🔍 追问点:requeue=true 有什么风险?(消息反复失败导致"毒消息"循环)
-
⚠️ 易错点:毒消息必须有限次重试,超过次数进 DLQ。
Q8:延迟消息怎么做?
-
✅ 背诵版:常见方案:TTL + DLQ 转发实现延迟;或用延迟插件(视环境支持)。
-
🧠 展开版:
-
TTL 到期进入 DLQ,再由 DLQ 消费者转发到目标队列
-
适用于:超时未支付关闭订单、定时重试
-
-
🔍 追问点:TTL 是队列级还是消息级?(都可以,实际看配置)
-
⚠️ 易错点:延迟堆太多会导致队列膨胀,需评估容量。
10.3 一页速背清单
-
用 MQ:削峰/解耦/异步(代价:一致性与治理)
-
模型:Producer → Exchange → Queue → Consumer
-
交换机:Direct/Topic/Fanout
-
不丢:confirm + 持久化 + 手动 ack + DLQ
-
幂等:业务唯一键 + 幂等表/唯一约束/Redis 去重
-
顺序:同 key 同队列 + 单消费者(或分片队列)
-
积压:扩消费/限流/优化/隔离失败
-
延迟:TTL + DLQ(或插件)
11. Linux / 网络 / Nginx
11.1 知识点速览
11.1.1 Linux 排查"三板斧"
-
看资源:CPU / 内存 / 磁盘 / 网络
-
看进程线程:是谁在占资源?卡在哪?
-
看日志链路:应用日志 / Nginx 日志 / 慢 SQL / 依赖超时
11.1.2 常用命令速查
-
系统:
top/htopuptimefree -mvmstatiostatdf -hdu -sh -
进程:
ps -efpstreepidstatlsof -plsof -i:PORT -
网络:
ss -lntpss -scurl -vpingtraceroutetcpdump -
日志:
tail -fgrepawksedlesszless -
服务:
systemctl statusjournalctl -u xxx -f
11.1.3 Nginx 常见职责
- 反向代理、负载均衡、静态资源、TLS 终止、限流、缓存、灰度发布
11.2 面试题库:Linux
Q1:Linux 下怎么查看 CPU/内存/磁盘/负载?
-
✅ 背诵版:
-
CPU/负载:
top/uptime -
内存:
free -m -
磁盘:
df -h(容量)+du -sh(目录占用)
-
-
🧠 展开版:
-
load average看 1/5/15 分钟队列压力(不是"CPU使用率") -
内存要看:available、swap(swap 大量使用通常性能会抖)
-
-
🔍 追问点:load 很高但 CPU 不高可能是什么?(IO 等待、锁竞争、线程阻塞)
-
⚠️ 易错点:把 load 当作 CPU 使用率,这是典型误解。
Q2:怎么查某个端口被谁占用?
-
✅ 背诵版:
ss -lntp | grep 8080或lsof -i:8080 -
🧠 展开版:拿到 PID 后
ps -ef | grep PID查看进程信息。 -
🔍 追问点:如何优雅重启服务?(systemctl / 发送信号)
-
⚠️ 易错点:线上别随便
kill -9,除非确认无解且有兜底。
Q3:日志太大怎么快速定位错误?
-
✅ 背诵版:
tail -n 200看最新、grep "ERROR"过滤、less分页搜索。 -
🧠 展开版:
-
按时间范围过滤(配合日志格式)
-
看"请求ID/traceId"串联链路(如果有)
-
-
🔍 追问点:怎么统计某接口报错次数?(grep + wc/awk)
-
⚠️ 易错点:直接打开超大日志文件(会卡),用
less/zless。
Q4:线上 CPU 100% 你怎么排查?
-
✅ 背诵版:
top找进程 → 看线程 → 抓栈(jstack)→ 定位死循环/热点方法。 -
🧠 展开版(标准套路):
-
top/监控:确认 PID -
top -H -p PID:找最耗 CPU 的线程 -
把线程 ID 转 16 进制,对照
jstack找栈 -
判断:死循环、频繁 GC、锁竞争、正则灾难、日志风暴等
-
-
🔍 追问点:如果是 GC 引起的 CPU 高怎么办?(看 GC log / jstat)
-
⚠️ 易错点:只会说"重启",不讲定位过程会被扣分。
Q5:线上内存一直涨(疑似内存泄漏)怎么排查?
-
✅ 背诵版:先确认是否缓存无界/对象堆积 → 导出堆 dump 分析引用链 → 修复并加监控。
-
🧠 展开版:Java 常见手段:OOM 自动 dump、jmap dump、MAT/IDEA 分析(讲思路即可)。
-
🔍 追问点:Redis/本地缓存导致的"逻辑泄漏"怎么识别?
-
⚠️ 易错点:内存涨不一定是泄漏,也可能是"缓存策略/峰值流量"。
11.3 面试题库:网络
Q6:TCP 三次握手/四次挥手?为什么要三次?
-
✅ 背诵版:三次握手建立连接确认双方收发能力;四次挥手因为 TCP 全双工,关闭要双方分别确认。
-
🧠 展开版:
-
三次:SYN → SYN+ACK → ACK
-
四次:FIN → ACK → FIN → ACK
-
-
🔍 追问点:TIME_WAIT 是什么?为什么会很多?
-
⚠️ 易错点:把 TIME_WAIT 说成"异常",它是协议设计的一部分。
Q7:TIME_WAIT 太多会怎样?怎么缓解?
-
✅ 背诵版:短连接高频会导致 TIME_WAIT 多;缓解:连接复用(keep-alive)、合理端口/内核参数、反向代理层优化。
-
🧠 展开版:
-
优先策略:业务层复用连接(HTTP keep-alive、连接池)
-
Nginx/网关层可承接大量短连接
-
-
🔍 追问点:CLOSE_WAIT 多意味着什么?(应用没正确 close)
-
⚠️ 易错点:只会调内核参数,不考虑"连接模型"与"上游行为"。
Q8:HTTP 和 HTTPS 区别?TLS 在解决什么?
-
✅ 背诵版:HTTPS = HTTP + TLS,加密/防篡改/身份认证。
-
🧠 展开版:
-
证书验证服务器身份
-
对称加密传数据,非对称协商密钥
-
-
🔍 追问点:为什么 HTTPS 仍可能被抓包?(终端信任根证书/代理、或应用层明文日志)
-
⚠️ 易错点:HTTPS 不是"绝对安全",还要配合安全配置与证书管理。
Q9:DNS 解析流程你了解吗?
-
✅ 背诵版:浏览器/系统缓存 → 本地 DNS → 递归解析(根→顶级→权威)→ 返回 IP 并缓存。
-
🧠 展开版:线上常见问题:DNS 缓存、解析超时、域名劫持(点到为止)。
-
🔍 追问点:怎么快速验证 DNS 问题?(nslookup/dig/curl)
-
⚠️ 易错点:把"访问慢"只归因应用,DNS 也可能是瓶颈之一。
11.4 面试题库:Nginx
Q10:什么是反向代理?和正向代理区别?
-
✅ 背诵版:反向代理代理服务器端,对客户端透明;正向代理代理客户端,对服务端透明。
-
🧠 展开版:
- Nginx 常做反向代理:统一入口、隐藏内部服务、做负载均衡与 TLS
-
🔍 追问点:为什么反代能保护后端?(隔离、统一鉴权/限流)
-
⚠️ 易错点:反向代理≠负载均衡,但它常承载负载均衡能力。
Q11:Nginx 负载均衡策略有哪些?怎么选?
-
✅ 背诵版:轮询、权重、ip_hash、最少连接等;一般默认轮询,粘性会话可用 ip_hash 或业务层 token。
-
🧠 展开版:
-
轮询:通用
-
权重:按机器性能分配
-
ip_hash:简单粘性,但会导致不均衡
-
最少连接:连接耗时差异大时更稳
-
-
🔍 追问点:健康检查怎么做?(应用层探活/网关探活/容器编排)
-
⚠️ 易错点:粘性会话不是万能,更推荐"无状态服务 + session 外置(Redis)"。
Q12:Nginx 常用来做哪些事情?
-
✅ 背诵版:反代、负载均衡、静态资源、TLS 终止、限流、缓存、灰度。
-
🧠 展开版:
-
限流:保护后端(连接数/QPS)
-
缓存:热点静态资源(或 API 缓存,需谨慎一致性)
-
-
🔍 追问点:限流是在哪一层做更合理?(网关/Nginx 优先,应用层兜底)
-
⚠️ 易错点:缓存动态接口要考虑"鉴权、个性化、失效"。
Nginx 反代配置示例(你放文章里很实用)
upstream app_upstream {
server 10.0.0.1:8080 weight=2;
server 10.0.0.2:8080 weight=1;
keepalive 64;
}
server {
listen 80;
server_name example.com;
location /api/ {
proxy_pass http://app_upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 2s;
proxy_read_timeout 10s;
}
}
11.5 一页速背清单
-
Linux:top/free/df/du/ps/ss/lsof/tail/grep
-
排查:现象→资源→进程线程→日志链路→验证修复
-
网络:TCP 握手挥手、TIME_WAIT/CLOSE_WAIT、HTTPS/TLS、DNS
-
Nginx:反代/负载/静态/TLS/限流/缓存/灰度
12. 分布式 / 高并发场景题
12.1 通用答题模板
面试官问场景题,你按这 6 步答,基本不会乱:
-
目标:一致性优先还是可用性优先?吞吐/延迟指标?
-
边界:峰值 QPS、数据量、允许延迟、允许丢失吗?
-
核心链路:读写路径怎么走(缓存/DB/MQ)
-
关键保障:限流/降级/熔断/重试/幂等/监控
-
数据一致性策略:最终一致 or 强一致(选方案并说明取舍)
-
故障预案:积压、超时、回滚、补偿、告警
12.2 高频场景题库
场景 1:秒杀/抢购如何设计?
-
✅ 背诵版:前置限流+库存预扣+异步下单+削峰填谷+最终一致+兜底补偿。
-
🧠 展开版(经典架构):
-
网关/Nginx 限流(令牌桶/漏桶)+ 黑白名单
-
Redis 预热库存:原子扣减(Lua)防超卖
-
下单写 MQ:异步创建订单(削峰)
-
消费端落库:幂等(订单号/请求ID)
-
失败重试 + DLQ,最终对账补偿
-
-
🔍 追问点:怎么防止机器人?(验证码/风控/滑块/限频)
-
⚠️ 易错点:只说"加 Redis",不讲"原子扣减 + 异步削峰"。
场景 2:库存扣减如何防超卖?
-
✅ 背诵版:核心是原子性:DB 乐观锁/行锁 或 Redis Lua 原子扣减;配合补偿与对账。
-
🧠 展开版:
-
DB 方案:
update stock set cnt=cnt-1 where id=? and cnt>0(原子条件更新) -
Redis 方案:Lua 扣减 + MQ 落库(最终一致)
-
-
🔍 追问点:高并发下用哪种更稳?(Redis 扛并发,DB 做最终落地)
-
⚠️ 易错点:先查库存再扣减(非原子)会超卖。
场景 3:下单后要通知库存/物流,怎么做异步?
-
✅ 背诵版:用 MQ 事件驱动:下单成功发消息,库存/物流订阅;保证不丢+幂等+失败重试。
-
🧠 展开版:
-
事件:OrderCreated
-
可靠性:生产 confirm + 持久化 + 消费 ack
-
幂等:用业务 ID(orderId)去重
-
-
🔍 追问点:如何保证"本地事务 + 发消息"一致?(见场景 4)
-
⚠️ 易错点:同步调用多个下游导致主链路变慢且易雪崩。
场景 4:如何保证"写 DB + 发 MQ"不丢不重?
-
✅ 背诵版:推荐 Outbox 本地消息表:同一事务写业务表+消息表,异步投递 MQ,失败可重试,最终一致。
-
🧠 展开版(面试很加分):
-
事务内:写订单 + 写 outbox(msg, status=NEW)
-
后台任务/CDC:扫描 NEW → 发 MQ → 标记 SENT
-
消费端幂等:按 msgId/orderId 去重
-
-
🔍 追问点:不用分布式事务也能做一致吗?(可以,最终一致)
-
⚠️ 易错点:业务写成功但发消息失败(或反过来)= 经典不一致坑。
场景 5:分布式锁怎么选?怎么避免死锁/误删?
-
✅ 背诵版:常用 Redis 锁:SET NX PX + 唯一 value + Lua 校验释放;长任务要续期;失败要退避重试。
-
🧠 展开版:锁是"降低并发冲突"的手段,不是万能;能用 DB 唯一约束/幂等表就别强上锁。
-
🔍 追问点:锁过期任务未完成?(续期/看门狗/拆分任务)
-
⚠️ 易错点:不校验 value 直接 del,会误删别人锁。
场景 6:接口幂等怎么设计?
-
✅ 背诵版:给请求一个唯一 requestId,服务端做"只处理一次":幂等表/唯一约束/Redis SETNX。
-
🧠 展开版:
-
幂等表:requestId + status + result
-
唯一索引:插入冲突即认为重复
-
返回一致:重复请求返回相同结果
-
-
🔍 追问点:幂等键怎么生成?(客户端生成/网关生成/服务端返回)
-
⚠️ 易错点:只做"去重",不保证"重复返回同结果"。
场景 7:如何做限流、降级、熔断?
-
✅ 背诵版:入口限流(Nginx/网关)优先;服务内降级兜底;依赖超时与熔断保护;监控告警闭环。
-
🧠 展开版:
-
限流:令牌桶/漏桶(保护系统容量)
-
降级:返回兜底数据/关闭非核心功能
-
熔断:依赖失败率高时快速失败,避免雪崩
-
-
🔍 追问点:你会看哪些指标?(QPS、RT、错误率、拒绝率、队列长度)
-
⚠️ 易错点:只在服务里做限流,入口不控流 DB 仍会被打爆。
场景 8:缓存一致性如何保证?
-
✅ 背诵版:常用"写 DB → 删缓存"(必要时延迟双删),并给删除失败做重试补偿;更强一致可用订阅变更驱动缓存更新。
-
🧠 展开版:读写并发下,删除缓存比更新缓存更稳;关键是补偿机制与监控。
-
🔍 追问点:缓存雪崩/击穿怎么防?(随机过期、互斥锁、逻辑过期)
-
⚠️ 易错点:删缓存失败不补偿,会出现长期脏数据。
场景 9:数据库读写分离/分库分表怎么落地?
-
✅ 背诵版:读写分离:主写从读,注意延迟;分库分表:按业务键分片,配合全局 ID、路由、中间件与扩容方案。
-
🧠 展开版:
-
读写分离:对一致性敏感读走主库
-
分片:选分片键(userId/orderId),避免跨分片查询
-
-
🔍 追问点:跨分片分页/排序怎么办?(尽量避免;或汇总服务/ES)
-
⚠️ 易错点:分库分表不是性能万能药,会显著增加复杂度。
场景 10:消息积压怎么处理?(MQ 必考)
-
✅ 背诵版:先定位瓶颈(消费慢/下游慢/失败重试),再扩容消费、批处理、限流、隔离失败、DLQ 告警。
-
🧠 展开版:增加消费者之前先评估 DB/下游承载,防止"扩容把 DB 打挂"。
-
🔍 追问点:毒消息怎么办?(限次重试→DLQ→人工/补偿)
-
⚠️ 易错点:无限重试导致积压更严重。
12.3 一页速背清单
-
高并发核心:限流削峰 + 异步解耦 + 幂等一致 + 可观测兜底
-
秒杀:Redis 原子扣减 + MQ 异步下单 + 对账补偿
-
一致性:Outbox / 最终一致(别硬上强分布式事务)
-
幂等:requestId / 幂等表 / 唯一约束
-
可靠性:重试要有上限 + DLQ + 告警
-
扩展:读写分离/分片要考虑延迟与复杂度