使用说明
面试先答:先记住这一段,面试时先给结论,再根据追问展开。详细解释:补充原理、流程、适用场景、性能权衡和版本差异。常见追问与易错点:用于二次复习,避免绝对化或混淆概念。项目经验与 HR 部分使用的是虚构 Java 校招候选人示例,必须替换成自己的真实经历和数据。
默认版本基线
答案默认兼容 Java 8 语法,并重点解释 JDK 17/21 的实现差异;Spring Boot 3/Spring 6;MySQL 8;Redis 7;主流 Kafka、RocketMQ、RabbitMQ 和 Spring AI 用法。涉及旧版本时会单独说明,不能把不同版本行为混为一谈。
去重说明
完全重复题合并为一个主答案,同时保留原始问法。
定义题、原理题、场景题若属于同一知识点,归入主问题的追问或变体,不复制整段答案。
跨章节重复知识保留一次完整解释,其他章节用“见前文”交叉引用。
题库中的 C++ 专属问题会明确标注为跨语言补充,不当作 Java 语法回答。
本次完全重复合并记录:进程和线程的区别是什么?、进程间的通信方式有哪些?、线程间的通信方式有哪些? 在操作系统章节保留入口,并指向 Java 并发章节的完整答案。
建议学习顺序
Java 基础 → 集合与并发 → JVM → MySQL/Redis → 网络与操作系统 → 消息队列 → Spring 与微服务 → 算法 → 场景设计 → 项目/安全/HR → AI 与 Agent。
目录
一、Java基础
1.1 语言特性与核心概念
1. Java相比其他语言(如C++、Go)有什么优势和特点?
面试先答: Java 以 JVM 为运行时,强调“一次编译、到处运行”、自动内存管理、面向对象和成熟生态;代价是启动和内存开销通常高于 Go,底层控制不如 C++。
详细解释: Java 源码编译为字节码,由 JVM 解释执行并通过 JIT 优化热点代码;垃圾回收减少手动内存错误,标准库、并发库和 Spring 等生态适合企业级服务。C++ 更接近硬件、性能上限高但需手动管理资源;Go 编译为原生程序、部署简单,语言和运行时更轻量。
示例或流程:
.java -> javac -> .class -> JVM(解释器/JIT) -> 机器码。常见追问与易错点: “跨平台”依赖目标平台上的 JVM;Java 并非绝对高性能,合理的 GC、JIT 预热和数据结构选择同样重要。
2. JDK、JRE、JVM、JIT的区别是什么?
面试先答: JVM 执行字节码;JRE = JVM + 运行库;JDK = JRE + 编译、调试等开发工具;JIT 是 JVM 内部把热点字节码编译为机器码的组件。
详细解释: JDK 8 的目录中通常可见 JRE,JDK 9 以后不再单独分发传统 JRE,但概念仍有意义。JIT 根据方法调用和循环计数进行内联、逃逸分析等优化。
示例或流程: 开发用
javac(JDK),线上只需 JVM 与模块化运行时;现代应用常用jlink生成精简运行镜像。常见追问与易错点: JIT 不是独立运行环境;HotSpot 的 C1/C2 编译器和分层编译属于实现细节,不要把 JRE 说成“只有 JVM”。
3. 什么是Java字节码?有什么作用?
面试先答: 字节码是编译器生成的与平台无关的指令和元数据,通常存于
.class文件,由 JVM 校验、加载、解释或编译执行。详细解释: class 文件包含魔数、版本、常量池、字段、方法和属性;字节码指令操作 JVM 的操作数栈和局部变量表。它隔离了源码与 CPU 指令,便于跨平台、动态加载和工具分析。
示例或流程:
javap -c Demo.class可查看方法字节码;类加载器先验证格式和安全性,再执行。常见追问与易错点: 字节码不是 Java 独有的机器码,也不是完全解释执行;不同 JDK 编译出的 class 版本可能不兼容。
Java 字节码(Bytecode)是 Java 源代码经过编译器编译后生成的一种中间指令,通常存放在 .class 文件中。
例如:
.java 源代码
↓ javac 编译
.class 字节码
↓ JVM 解释执行或 JIT 编译
机器指令它的主要作用:
实现跨平台:字节码不直接绑定某种 CPU 或操作系统,只要有对应的 JVM,就能运行同一个
.class文件,即“一次编译,到处运行”。便于 JVM 优化:JVM 可以通过 JIT 将热点字节码编译成本地机器码,提高运行速度。
提供安全检查:JVM 在执行前会校验字节码,检查类型、栈操作等是否合法。
支持动态特性:类加载、反射、动态代理和运行时优化等机制都以字节码为基础。
便于分发和部署:程序可以以
.jar等形式打包,包含编译后的字节码。
简单说,Java 字节码是连接 Java 源代码和具体计算机硬件之间的“中间语言”,JVM 就是负责理解和执行它的运行环境。
4. Java解释与编译共存的原因是什么?
面试先答: 解释执行启动快、兼容动态特性;JIT 编译热点代码运行快。两者结合兼顾启动时间、吞吐和运行时优化。
详细解释: JVM 先以解释器快速运行并收集统计信息,方法变热后由 C1/C2 编译并替换执行;反优化时可退回解释器。动态类加载、反射和运行时类型信息使“先运行再优化”很有价值。
示例或流程: 冷代码解释执行,热点循环触发分层编译,内联后减少调用开销。
常见追问与易错点:
javac只是把源码编译成字节码,不等于编译成当前 CPU 的最终机器码;AOT 也不能完全替代 JIT 的运行时反馈。
5. JIT与AOT的区别是什么?
面试先答: JIT 在运行时基于真实数据编译,适应性强但有预热和编译开销;AOT 在运行前生成原生代码,启动快、内存低但动态能力和跨平台灵活性较弱。
详细解释: GraalVM Native Image、Spring AOT 属于 AOT 方向;HotSpot C1/C2 属于 JIT。JIT 可针对当前 CPU、分支概率和调用热点做优化,AOT 优化依赖构建期可见信息。
示例或流程: 容器短任务偏好 Native Image;长时间运行的交易服务通常受益于 HotSpot JIT。
常见追问与易错点: AOT 不是“没有 JVM”,Native Image 仍带有自己的运行时;AOT 可能需要额外配置反射、动态代理和资源。
6. Java八大基础数据类型有哪些?各自的取值范围?
面试先答:
byte8 位、short16 位、int32 位、long64 位;float32 位、double64 位;char16 位无符号 UTF-16 单元;boolean只有 true/false(规范不规定存储位数)。详细解释: 整数为补码,范围分别为 -2^7~2^7-1、-2^15~2^15-1、-2^31~2^31-1、-2^63~2^63-1;浮点遵循 IEEE 754,存在舍入误差。
char表示 UTF-16 代码单元,一个完整 Unicode 码点可能需要两个char。示例或流程: 金额不用
float/double直接累加,应使用BigDecimal或最小货币单位整数。常见追问与易错点:
long字面量大数要加L;boolean没有可移植的“占 1 位”保证;字符编码和char不是一回事。
7. 基本类型和包装类的区别是什么?
面试先答: 基本类型是值,存储和运算开销小;包装类是对象,可为 null、可用于泛型和集合,并提供工具方法。
详细解释:
Integer等包装类有对象头和引用,比较应注意equals与缓存;泛型类型擦除要求使用包装类。拆箱遇到 null 会抛NullPointerException。示例或流程:
List<Integer>不能写List<int>;数据库可空字段映射为Integer而非int。常见追问与易错点: 包装类不可变;频繁装箱会产生对象和 GC 压力,可用原始数组或专用集合优化。
8. 包装类的缓存机制了解吗?
面试先答: 包装类通常缓存部分常用值,
Integer默认缓存 -128~127;缓存范围和实现可配置,不能用==判断数值相等。详细解释:
Integer.valueOf优先返回缓存对象,new Integer(已废弃)强制新建。Byte/Short/Long/Character/Boolean也有相应缓存策略,Float/Double通常不缓存。示例或流程:
Integer a=127,b=127可能a==b为真,128则不应依赖结果;正确写法Objects.equals(a,b)。常见追问与易错点: 缓存是实现优化,不是业务契约;自动装箱调用的是
valueOf,而不是无条件new。
9. 自动装箱拆箱的原理是什么?for循环中使用包装类有什么问题?
面试先答: 编译器把基本值转成
valueOf调用,把包装对象转成xxxValue调用;包装类在循环中可能引入 NPE、装箱分配和性能损耗。详细解释:
Integer x=1近似Integer.valueOf(1),int y=x近似x.intValue()。for(Integer i=0; i<list.size(); i++)每轮可能拆箱,i++又装箱;i为 null 会 NPE。示例或流程: 大循环使用
for (int i=0; i<n; i++);集合元素先校验 null 再拆箱。常见追问与易错点:
i += 1对包装类也会拆箱再装箱;不要把缓存行为当作性能保证。
10. 浮点数为什么会精度丢失?如何解决?
面试先答: 二进制浮点无法精确表示许多十进制小数,运算会产生舍入误差;金额使用
BigDecimal(字符串构造)或整数分,比较设置误差范围。详细解释:
0.1在 IEEE 754 中是无限循环二进制,存储时被截断;多次运算误差会累积。BigDecimal(double)会把已有误差带入,推荐BigDecimal.valueOf(double)或字符串构造。示例或流程:
new BigDecimal("0.1").add(new BigDecimal("0.2"))得到精确的0.3。常见追问与易错点:
double适合科学计算但不适合财务结算;BigDecimal.equals还比较 scale,数值比较用compareTo。
11. 为什么说对象可能不在堆中?
面试先答: Java 规范要求对象语义而非固定物理位置;JIT 通过逃逸分析和标量替换,可能把未逃逸对象分配到栈上或直接消除。
详细解释: 线程逃逸对象不能跨线程,方法逃逸对象只在方法内可见;若对象字段可拆成标量,JIT 可消除分配。即使通常分配在堆,也可能被 TLAB、压缩指针等优化改变布局。
示例或流程:
Point p=new Point(); return p.x+p.y;在优化后可能只保留两个局部数值。常见追问与易错点: 逃逸分析是实现优化,不能写业务代码依赖“对象一定在栈”;使用
-XX:-DoEscapeAnalysis可用于实验。
12. 基本数据类型的存储位置?堆里还是栈里?
面试先答: 取决于变量位置和 JIT 优化:方法局部变量通常在栈帧,实例字段随对象在堆,静态字段在类元数据关联区域;规范不保证具体物理位置。
详细解释: 栈帧的局部变量表保存引用或基本值;对象字段属于对象,通常在堆;类静态字段随类生命周期管理。标量替换可能让对象字段也不落堆。
示例或流程:
int x是局部值,obj.x是对象字段;“基本类型都在栈、引用都在堆”是过度简化。常见追问与易错点: 引用本身可能在栈,引用指向的对象通常在堆;元空间主要存类元数据而非普通实例字段。
13. 超过long范围的整型数据怎么表示?
面试先答: 用
BigInteger表示任意精度整数;它以数组保存多个机器字,运算复杂度随位数增加。详细解释:
BigInteger不可变,支持加减乘除、位运算和模运算;构造时可传字符串避免字面量溢出。性能要求极高且范围已知时,也可用高低位数组自行编码。示例或流程:
new BigInteger("9223372036854775808").add(BigInteger.ONE)。常见追问与易错点:
BigInteger不能用==比较数值;intValue()超范围会截断,需使用longValueExact等检查方法。
14. ++、--运算符的使用注意事项?
面试先答: 前置先加后用,后置先用后加;复合表达式中容易因求值顺序和副作用出错,并发下自增不是原子操作。
详细解释:
int a=1; int b=a++得到b=1,a=2。i++可拆成读、加、写,多线程应使用AtomicInteger.incrementAndGet或锁。示例或流程: 避免
arr[i++]=i++一类代码,拆成多条语句并明确顺序。常见追问与易错点: Java 从左到右求值,但不要依赖复杂表达式;
volatile int仍不能保证自增原子性。
15. const int p和int const p有什么区别?
面试先答: 这是 C/C++ 指针语法,不是 Java 语法。
const int *p表示不能通过 p 修改所指整数;int* const p表示 p 本身不能改指向,但可修改所指整数。详细解释: Java 没有裸指针和
const,用final限制引用重新赋值,用不可变对象或封装限制对象状态修改;final引用不等于对象不可变。示例或流程: C++
const int* p=&x;禁止*p=...;Javafinal List<Integer> l仍可l.add(...)。常见追问与易错点: 不要把 C++ 的 const-correctness 直接类比成 Java
final;Java 的 JNI/Unsafe 属于特殊底层接口。
1.2 面向对象编程
1. 面向对象的三大特性是什么?分别怎么理解?
面试先答: 封装隐藏实现、继承复用和扩展、多态让同一抽象在不同对象上表现不同;现代 Java 也强调组合优于继承。
详细解释: 封装通过访问修饰符和接口约束状态;继承建立 is-a 关系;多态依赖重写、接口实现和动态分派。三者共同降低耦合,提高可替换性。
示例或流程:
List接口引用可以指向ArrayList或LinkedList,调用add时执行各自实现。常见追问与易错点: 继承不是简单复制代码;多态不包括静态方法的动态分派,字段也不按运行时类型多态。
2. 什么是多态?多态的实现方式有哪些?
面试先答: 多态是父类型引用在运行时指向不同子类型,并调用其重写方法;主要通过继承、接口实现和方法重写实现。
详细解释: 编译器按引用的静态类型检查可调用方法,JVM 在运行时根据对象实际类型进行虚方法分派;重载属于编译期多态,重写属于运行期多态。
示例或流程:
Animal a=new Dog(); a.sound()执行Dog.sound()。常见追问与易错点:
static/private/final方法不能被真正重写;构造器不参与多态。
3. 方法重写和方法重载的核心区别是什么?
面试先答: 重写发生在父子类、签名相同、运行时决定;重载发生在同一类或继承体系、参数列表不同、编译期选择。
详细解释: 重写不能降低访问权限,返回值可协变,受检异常不能更宽;重载只看方法名和参数,不以返回值区分。
示例或流程:
void f(int)与void f(String)是重载;子类重新实现void run()是重写。常见追问与易错点:
@Override可帮助编译器检查;泛型擦除后可能导致重载签名冲突。
4. 成员变量和局部变量的区别是什么?
面试先答: 成员变量属于对象或类,有默认值并随对象/类生命周期存在;局部变量属于方法或代码块,必须显式初始化且作用域更小。
详细解释: 成员变量可用访问修饰符,实例字段每个对象一份,静态字段类级共享;局部变量存于栈帧,不能用访问修饰符和
static(除参数等特殊形式)。示例或流程:
int field;默认 0,int local; System.out.println(local);编译失败。常见追问与易错点: 默认值只适用于字段和数组元素;局部变量名可遮蔽字段,需要
this.field区分。
5. 成员变量为什么要有默认值?
面试先答: JVM 在对象分配和类初始化时统一清零,保证可预测性和安全性,避免读取未初始化内存;局部变量则要求开发者明确初始化以暴露错误。
详细解释: 默认值包括数值 0、boolean false、引用 null、char '\u0000';随后执行显式初始化和构造器。数组元素也遵循默认值。
示例或流程:
new User().name == null,但User u; u.name不能编译,因为局部引用未初始化。常见追问与易错点: 默认值不代表业务有效值,构造器仍应建立不变式;
null解引用会导致 NPE。
6. 静态方法和实例方法的区别是什么?
面试先答: 静态方法属于类、无
this、通过类名调用;实例方法属于对象、可访问实例状态并参与动态分派。详细解释: 静态方法在编译期按引用静态类型绑定,不能直接访问实例字段;实例方法调用需要对象,支持重写。工具类方法适合静态,依赖对象状态的行为适合实例。
示例或流程:
Math.max是静态方法;list.add是实例方法。常见追问与易错点: 子类可隐藏静态方法但不是重写;静态方法中不能直接使用
this/super。
7. 继承、接口、抽象类的区别是什么?各自的应用场景和局限性?
面试先答: 继承复用一个父类的实现并建立强 is-a;抽象类可共享状态和部分实现;接口描述能力契约,支持多实现。优先用接口和组合降低耦合。
详细解释: Java 类只能单继承,接口可多实现并有 default/static 方法;抽象类可有构造器和实例字段,接口字段默认 public static final。抽象类适合同一产品族的共享骨架,接口适合跨层能力约束。
示例或流程:
ArrayList继承AbstractList并实现List;Runnable是接口契约。常见追问与易错点: default 方法冲突需显式解决;继承关系过深会脆弱,不能把接口当作“只能有抽象方法”(Java 8 以后不成立)。
8. 深拷贝和浅拷贝的区别是什么?
面试先答: 浅拷贝只复制对象本身,内部引用仍共享;深拷贝递归复制可变成员,副本互不影响。
clone默认是浅拷贝。详细解释: 可通过拷贝构造器、手写复制、序列化或映射工具实现深拷贝;不可变对象(如 String)共享通常安全。深拷贝成本高,应按业务边界选择。
示例或流程:
Order拷贝时若items仍指向同一List,一个对象新增商品会影响另一个。常见追问与易错点:
final引用不能重新指向但不阻止内部对象变化;序列化深拷贝要求对象图可序列化且有性能/安全风险。
9. Object类包含哪些方法?
面试先答: 常用有
equals、hashCode、toString、getClass、clone、finalize(已废弃)、wait、notify、notifyAll。详细解释:
wait/notify必须在对象监视器持有时调用;clone受保护且需实现Cloneable;finalize在 JDK 9 标记弃用、JDK 18 进一步限制,应用应使用try-with-resources或Cleaner。示例或流程: 集合先用
hashCode定位桶,再用equals判断相等。常见追问与易错点: Object 的
equals默认等同==;wait会释放锁,sleep不会。
10. == 和 equals的区别是什么?
面试先答: 基本类型
==比较值,引用类型==比较是否同一对象;equals是方法,默认仍比较引用,类可重写为值相等。详细解释: String、包装类等重写了
equals;比较包装类可能先拆箱。使用Objects.equals(a,b)可安全处理 null。示例或流程:
new String("a") == new String("a")为 false,而equals为 true。常见追问与易错点: 不要用
==比较字符串内容;重写 equals 必须同时满足自反、对称、传递、一致、非 null。
11. 为什么要同时重写equals和hashCode方法?
面试先答: 哈希集合要求相等对象必须有相同哈希值;只重写 equals 会导致 HashMap/HashSet 查找失败。
详细解释: HashMap 先按 hash 定位桶,再调用 equals;hash 相同不代表对象相等。参与计算的字段在放入集合后不应改变,否则对象可能“丢失”。
示例或流程: 使用
Objects.hash(id,name)与Objects.equals实现值对象。常见追问与易错点: 不要求不相等对象 hash 一定不同;hashCode 不能依赖随机值或可变字段。
12. final关键字的作用是什么?
面试先答: final 变量只能赋值一次,final 方法不能被重写,final 类不能被继承;它用于表达不变性和设计约束。
详细解释: final 引用只保证引用不变,不保证对象内部状态不变;final 字段可在声明处、初始化块或构造器赋值。安全发布语义中 final 字段有额外保证。
示例或流程:
final List<String> xs=new ArrayList<>(); xs.add("a")合法,但xs=new ...不合法。常见追问与易错点: final 不等于编译期常量,必须是基本类型/String 且初始化表达式可折叠;不可变类需私有 final 字段、无修改器和防御性复制。
13. static关键字的作用是什么?
面试先答: static 成员属于类而非实例,常用于类变量、类方法、静态初始化块和静态嵌套类。
详细解释: 类加载时初始化静态字段和代码块,按源文件顺序执行;静态成员生命周期通常与类加载器一致。静态嵌套类不持有外部类实例引用。
示例或流程: 单例的静态内部类利用类初始化的线程安全保证;工具类构造器应私有化。
常见追问与易错点: 静态字段在多实例间共享,需关注并发;类卸载受类加载器和引用关系影响。
14. 封装特性在项目中的具体体现?
面试先答: 通过 private 字段、公开行为方法、接口和模块边界隐藏实现,让调用方依赖稳定契约并集中校验。
详细解释: DTO 与领域对象分离,构造器/工厂保证状态合法,Repository 隐藏持久化细节;权限、校验和事务边界也属于封装。
示例或流程:
Account.withdraw(amount)内部校验余额,外部不能直接修改balance。常见追问与易错点: 只生成 getter/setter 不等于良好封装;不要泄露可变集合,返回不可修改视图或副本。
1.3 String相关
1. String、StringBuffer、StringBuilder的区别是什么?
面试先答: String 不可变;StringBuilder 可变且非线程安全、性能高;StringBuffer 可变并用 synchronized 保证线程安全,通常更慢。
详细解释: 多次拼接应使用 StringBuilder 或编译器生成的 builder;跨线程共享可用 StringBuffer,但更常见是线程封闭或外部同步。JDK 9 后 String 内部采用 byte[]+coder 压缩存储。
示例或流程: 循环拼接
StringBuilder sb,最后sb.toString()。常见追问与易错点: StringBuilder 不是“绝对线程安全”;StringBuffer 的单次方法同步不等于复合操作原子。
2. String底层为什么用byte[]而不是char[]?
面试先答: JDK 9 引入 Compact Strings,用 byte[] 配合 coder 表示 Latin-1 或 UTF-16,纯拉丁字符可节省约一半内存;不满足时仍用 UTF-16。
详细解释: String 的不可变性保证共享安全,byte[] 由构造过程复制防止外部修改。JDK 8 及更早通常是 char[],回答要区分版本。
示例或流程: 英文日志多为 Latin-1 编码,使用 byte[];含中文则退化为 UTF-16 表示。
常见追问与易错点:
char仍是 UTF-16 单元,不能表示所有码点;不要声称 Java 17 的 String 永远是 byte[] 单字节。
3. 直接使用+拼接字符串和使用StringBuilder有什么区别?
面试先答: 编译期常量的
+会折叠;单条语句中的变量拼接通常由编译器生成 StringConcatFactory/builder;循环中反复+可能产生大量临时对象,应显式 StringBuilder。详细解释: JDK 9 的 invokedynamic 拼接策略会按运行时类型选择实现,但不会自动把跨循环拼接优化成一个 builder。builder 可预估容量减少扩容。
示例或流程:
for中sb.append(x),结束后转字符串;简单日志参数可使用占位符避免无效拼接。常见追问与易错点:
+可读性更好,不能机械替换;编译器优化依赖上下文,不要以反编译结果作为语言规范。
4. String的equals和Object的equals有什么区别?
面试先答: Object.equals 默认比较引用;String 重写为按字符序列比较,并先比较长度、编码和内容。
详细解释: String 具有不可变性,因此 hashCode 可缓存;equals 对 null 返回 false,对非 String 返回 false。字符串比较应使用 equals 或常量在左侧调用。
示例或流程:
"abc".equals(input)可避免 input 为 null 时 NPE。常见追问与易错点: equals 区分大小写;需要忽略大小写可用
equalsIgnoreCase,不要先toLowerCase忽略本地化问题。
5. 字符串常量池是什么?有什么作用?
面试先答: 常量池维护字符串字面量的唯一规范实例,减少重复对象和内存;现代 HotSpot 位于 Java 堆中。
详细解释: 类加载时字面量进入池,运行时
intern()可返回池中实例;编译期常量表达式可能直接复用同一引用。池是全局共享的,过度 intern 会增加元数据和查找压力。示例或流程:
String a="x", b="x"通常引用同一池对象;new String("x")先创建新对象。常见追问与易错点: 常量池不等于永久代(JDK 8 已在堆);池相等不意味着业务字符串都适合 intern。
6. String s = new String("abc")创建了几个字符串对象?
面试先答: 若常量池中没有
"abc",执行时通常创建池中对象和堆中新 String 对象,共两个;若池中已有,则只新建堆对象,共一个新增对象。详细解释: 字面量在类加载/解析时进入池;
new String(char[])等构造会复制内容并创建独立对象。具体优化不应依赖对象计数细节。示例或流程:
s.intern()=="abc"通常为 true。常见追问与易错点: “创建两个”是面试近似说法,需说明池中是否已有以及 JDK 实现;不要用
==代替 equals。
7. String的intern()方法是干嘛的?
面试先答: intern 返回字符串规范实例:池已有则返回池对象,否则将当前字符串(或其副本,依 JDK 实现)放入池并返回池引用。
详细解释: 它适合少量、重复度高且生命周期可控的枚举型字符串;大量用户输入 intern 会使堆中池结构膨胀。JDK 7 起字符串池位于堆,行为与早期永久代不同。
示例或流程:
new String("a").intern()=="a"为 true。常见追问与易错点: intern 不改变原变量;不要把它当作通用缓存或并发去重方案。
1.4 异常处理
1. Java异常的分类有哪些?
面试先答: Throwable 下分 Error 和 Exception;Exception 又分受检查异常和 RuntimeException。Error 通常表示 JVM/系统严重故障,不应常规捕获。
详细解释: 受检查异常在编译期要求声明或捕获;运行时异常多表示编程错误,如 NPE、越界、非法参数。异常对象携带类型、消息和堆栈。
示例或流程:
IOException需处理,NullPointerException通常修复代码逻辑。常见追问与易错点:
Error也继承 Throwable;不要捕获Throwable后吞掉所有问题。
2. 受检查异常和不受检查(运行时)异常的核心区别是什么?
面试先答: 受检查异常由编译器强制处理,表达可恢复的外部失败;运行时异常无需声明,常表示调用方违反前置条件或程序缺陷。
详细解释: 设计 API 时应选择能被调用方合理恢复的 checked exception;业务层可将底层异常转换为统一领域异常并保留 cause。Spring 事务默认只对 RuntimeException 回滚(可配置)。
示例或流程: 文件不存在可提示用户重试;参数为 null 应在入口校验而不是层层捕获 NPE。
常见追问与易错点: checked 不代表一定可恢复,RuntimeException 也可被捕获;不要为了消除编译错误把所有异常包装成 RuntimeException。
3. try-catch-finally的执行顺序是怎样的?
面试先答: 先执行 try;发生匹配异常则执行对应 catch;无论是否异常,正常离开 try/catch 前执行 finally(除非 JVM 终止等特殊情况)。
详细解释: try-with-resources 会先按逆序关闭资源,再执行 catch/finally;关闭异常可能被抑制并挂在主异常上。finally 适合清理,不适合改变返回值。
示例或流程:
try -> catch(可选) -> finally -> 方法返回。常见追问与易错点: catch 顺序应从子类到父类;不要在 finally 再抛出无关异常覆盖原异常。
4. try、catch、finally都有return时怎么执行?
面试先答: finally 中的 return 会覆盖 try/catch 的返回值并吞掉异常;因此应避免在 finally return。返回表达式的值通常先求出,再执行 finally。
详细解释:
try计算返回值后保存,finally 修改局部变量通常不影响已保存的基本值,但若返回引用指向可变对象,finally 可改变对象内容。finally 抛异常也会覆盖原结果。示例或流程:
try{return 1;} finally{return 2;}最终返回 2。常见追问与易错点: “finally 一定覆盖”只在 finally 自身 return/抛异常时成立;正常 finally 不会改变已确定的基本返回值。
5. finally一定会执行吗?哪些情况不会执行?
面试先答: 通常会执行,但
System.exit、JVM 崩溃/断电、进程被强制杀死等情况下不能保证。详细解释: 线程被
Thread.stop等非正常终止、无限循环卡在 try 中也可能无法到达 finally。资源释放应优先用 try-with-resources。示例或流程:
try (InputStream in=...) { ... }编译成隐式 finally 关闭资源。常见追问与易错点: finally 不是绝对可靠的持久化机制;不要在其中执行不可恢复的关键业务提交。
6. 异常的正确使用方式有哪些?
面试先答: 只捕获能处理的异常,保留 cause 和上下文,尽早失败并在边界统一转换;避免空 catch、异常控制正常流程和过度打印堆栈。
详细解释: 使用具体异常类型和有意义消息;日志在合适边界记录一次,避免重复打印。资源用 try-with-resources,定义业务异常层次,必要时设置超时和重试上限。
示例或流程:
catch(IOException e){ throw new ConfigLoadException("读取配置失败", e); }。常见追问与易错点: 不要把密码/token写进异常消息;重试需区分幂等性和可恢复性。
1.5 泛型与反射
1. 什么是泛型?泛型的作用和原理是什么?
面试先答: 泛型让类型参数化,在编译期提供类型安全、减少强转;Java 主要通过类型擦除保持与旧字节码兼容。
详细解释:
List<String>与List<Integer>编译期不同,运行时大多共享同一原始类;编译器插入强转并检查边界。泛型只接受引用类型,基本类型需包装。示例或流程:
class Box<T>{T value;};Box<String>取值无需手写 cast。常见追问与易错点: 不能
new T()、不能直接创建泛型数组;通配符? extends只读倾向,? super写入倾向。
2. 什么是泛型擦除?
面试先答: 编译后大多数类型参数被擦除为上界(无界为 Object),并插入必要强转;因此运行时不能直接判断
List<String>和List<Integer>的差异。详细解释: 桥接方法保持多态签名;泛型信息可通过 Signature 属性供反射读取,但不是运行时对象类型。重载不能只靠泛型参数区分。
示例或流程:
List<String>.class不存在,只有List.class。常见追问与易错点: 类型擦除不是“所有泛型信息完全消失”;反射读取的是类文件签名元数据。
3. 泛型的使用方式有哪些?
面试先答: 可用于泛型类、泛型接口、泛型方法、受限类型参数和通配符;API 设计遵循 PECS:生产者 extends,消费者 super。
详细解释:
<T extends Comparable<T>>表示上界;<?>表示未知类型;方法类型参数可独立于类。通配符适合灵活接收不同参数化类型。示例或流程:
static <T> T first(List<? extends T> xs);void addAll(List<? super Integer> out)。常见追问与易错点:
List<Object>不是List<String>的父类型;数组协变可能运行时抛ArrayStoreException。
4. 什么是反射?反射的原理是什么?
面试先答: 反射是在运行时检查类、字段、方法、构造器并动态调用的机制;JVM 通过 Class 元数据和反射 API 完成访问。
详细解释:
Class.forName或类字面量获取 Class,随后查找成员并在权限允许时调用;模块系统和强封装可能限制setAccessible。现代 JDK 还提供 MethodHandle/VarHandle 作为更高性能替代。示例或流程: 框架启动扫描注解 -> 反射创建 Bean -> 注入字段/调用方法。
常见追问与易错点: 反射不等于绕过所有安全检查;异常会包装为
InvocationTargetException,需查看 cause。
5. 反射的优缺点是什么?
面试先答: 优点是解耦、动态扩展、适合框架;缺点是性能和可读性较差、编译期检查弱、可能破坏封装并受模块限制。
详细解释: 缓存 Method/Constructor、使用 MethodHandle、减少热路径反射可降低成本;安全边界要校验类名和成员,避免任意代码执行。
示例或流程: 配置驱动加载实现类很灵活,但拼写错误只能运行时发现。
常见追问与易错点: JIT 可内联部分反射路径,但不能保证;不要为了“通用”在核心循环中反射。
6. 反射的常见应用场景有哪些?
面试先答: IoC/DI、ORM 映射、序列化、测试框架、插件加载、注解处理和代理生成。
详细解释: Spring 扫描类和注解,MyBatis 映射方法,JUnit 发现测试方法;编译期注解处理器可把一部分反射改为生成代码,提升启动性能。
示例或流程: 读取
@Autowired-> 找候选 Bean -> 解析依赖 -> 注入。常见追问与易错点: 生产环境需配置反射可达性(尤其 Native Image);不要从不可信输入直接反射任意类。
7. 什么是注解?注解的原理和使用场景是什么?
面试先答: 注解是附着在代码元素上的元数据,通过
Retention决定保留到源码、类文件还是运行时;工具或框架读取后执行约定逻辑。详细解释:
@Target限制使用位置,@Inherited只影响类级注解继承,@Repeatable支持重复标注。运行时注解由代理对象实现Annotation接口。示例或流程:
@Transactional被 Spring 解析,创建代理并在方法边界开启事务。常见追问与易错点: 注解本身不自动产生行为;运行时反射读取需要
RetentionPolicy.RUNTIME。
1.6 代理与SPI
1. JDK动态代理和CGLIB动态代理的底层实现差异是什么?
面试先答: JDK 代理基于接口和
InvocationHandler,生成实现接口的代理类;CGLIB/Byte Buddy 通过字节码生成子类,能代理具体类但不能代理 final 类/方法。详细解释: JDK 代理调用经过反射式 handler;CGLIB 使用方法拦截器,通常性能更好但生成类和类加载更复杂。Spring 依据是否有接口及配置选择代理技术,Spring 6 更常用 CGLIB/Byte Buddy 能力。
示例或流程:
Proxy.newProxyInstance-> handler;CGLIBEnhancer-> subclass override。常见追问与易错点: 自调用绕过代理;final 方法无法被子类覆盖;代理类要注意 equals/hashCode 语义。
2. 什么是AOP?AOP的原理是什么?由哪几个部分组成?
面试先答: AOP 把日志、事务、权限等横切关注点模块化,通过代理或字节码织入,在连接点执行通知。
详细解释: 核心概念包括连接点、切点、切面、通知和织入;Spring AOP 主要基于运行时代理,只拦截 Spring Bean 方法调用。AspectJ 可在编译期/类加载期织入更广范围。
示例或流程: 调用代理 -> 前置通知 -> 目标方法 -> 返回/异常通知 -> 后置通知。
常见追问与易错点: 切点表达式过宽会影响性能;AOP 不是“自动拦截所有方法”,private/final/构造器通常不在 Spring 代理范围。
3. 同一个类中方法相互调用不触发AOP的原因是什么?如何解决?
面试先答: 同类调用使用
this,绕过代理对象,切面没有机会拦截;可通过拆分 Bean、注入自身代理、AopContext.currentProxy()或 AspectJ 织入解决。详细解释: 最推荐把需要切面的职责拆到独立服务;自注入需避免循环依赖并开启 exposeProxy,AspectJ 适合更底层但构建复杂。
示例或流程:
serviceA.method1 -> serviceB.method2,由容器代理 serviceB。常见追问与易错点: 直接
this.method2()永远不会经过代理;AopContext依赖线程上下文,非 Spring 管理对象也不生效。
4. 什么是SPI?SPI的作用是什么?解决了什么问题?
面试先答: SPI 是服务提供者接口机制,调用方定义接口,第三方在
META-INF/services声明实现,由ServiceLoader发现,解决可插拔和解耦。详细解释: 它是“面向接口编程 + 运行时发现”,适合 JDBC 驱动、日志实现等;缺点是加载粒度粗、实例化和错误处理较弱。Spring Boot 的自动配置使用自己的 factories/imports 机制,但思想相近。
示例或流程:
ServiceLoader.load(MyProvider.class)-> 读取配置 -> 按需迭代实例。常见追问与易错点: JDK 9 模块 SPI 还需
provides ... with;SPI 与 API 不同,API 是调用方主动调用的公开接口。
5. 什么是模板元编程?有什么实际应用?
面试先答: 模板元编程通常指在编译期利用类型/模板计算生成代码;Java 没有 C++ 模板元编程语法,常用泛型、注解处理器、代码生成和宏式工具实现类似效果。
详细解释: Java 泛型主要保证类型安全,不进行任意编译期数值计算;MapStruct、Lombok、AutoService 通过注解处理器生成源码/字节码,减少运行时反射。
示例或流程:
@Mapper-> 编译期生成UserMapperImpl-> 运行时直接调用。常见追问与易错点: 不要把 Java 泛型等同于 C++ 模板实例化;生成代码需纳入构建、调试和版本管理策略。
6. C++的模板编程是什么?有什么优缺点?
面试先答: C++ 模板是编译期泛型和元编程机制,可按类型实例化函数/类并做编译期计算;优点是零运行时抽象成本,缺点是编译慢、错误信息复杂、代码膨胀。
详细解释: 模板支持特化、偏特化、SFINAE 和 C++20 concepts;实例化代码通常针对具体类型生成。Java 泛型擦除更强调兼容和运行时简洁,但牺牲了部分类型信息。
示例或流程:
template<class T> T max(T a,T b)在编译期生成不同类型版本。常见追问与易错点: 模板不是运行时反射;C++ 模板的约束可用 concepts 改善可读性。
1.7 IO与序列化
1. 什么是序列化和反序列化?原理是什么?
面试先答: 序列化把对象状态编码为字节或文本,反序列化再恢复为对象;格式可为 Java 原生、JSON、Protobuf 等。
详细解释: Java 原生序列化依据类描述和字段写入对象图,
serialVersionUID用于兼容校验;JSON/Protobuf 通过字段名或编号映射,跨语言和演进更好。示例或流程:
对象 -> 编码器 -> 网络/磁盘 -> 解码器 -> 对象。常见追问与易错点: 不要对不可信数据直接使用 Java 原生反序列化,存在 gadget 攻击;敏感字段应脱敏或排除。
2. 为什么需要序列化和反序列化?
面试先答: 进程内对象不能直接跨网络或持久化,序列化提供稳定的传输和存储表示,反序列化恢复可操作对象。
详细解释: 分布式 RPC、消息队列、缓存和快照都依赖编码;需考虑版本兼容、大小、速度、可读性和安全。Protobuf 等二进制格式通常比 JSON 更紧凑。
示例或流程: 服务 A 将请求编码后发送,服务 B 解码并校验 schema。
常见追问与易错点: 序列化不是深拷贝的通用替代;格式升级应兼容旧消费者,避免字段编号复用。
3. 字节流和字符流的区别是什么?
面试先答: 字节流处理原始 8 位数据,适合图片/网络协议;字符流基于
Reader/Writer按字符编码处理文本,需明确 charset。详细解释:
InputStreamReader是字节到字符的桥接,默认 charset 可能随机器变化,应显式指定 UTF-8。缓冲流减少系统调用,flush影响写出时机。示例或流程:
Files.newBufferedReader(path, UTF_8)读取文本;Files.copy复制二进制。常见追问与易错点: 字符不等于一个字节;乱码通常来自编解码字符集不一致。
4. BIO、NIO、AIO的区别是什么?
面试先答: BIO 每连接一个线程阻塞读写;NIO 以 Channel/Buffer/Selector 实现非阻塞多路复用;AIO 由操作系统完成异步 IO 后回调/完成通知。
详细解释: NIO 适合大量连接但业务仍需处理事件循环;Linux 上 Java AIO 常由线程池模拟,实际效果取决于平台。Netty 在 NIO/epoll/kqueue 上封装了高性能模型。
示例或流程: Selector 等待多个 channel 就绪 -> 读取 buffer -> 业务线程处理。
常见追问与易错点: 非阻塞不等于业务零等待;NIO Buffer 必须正确切换
flip/clear,AIO 不一定比 NIO 更快。
二、Java集合框架
2.1 集合体系基础
1. Java集合的继承体系是什么?
面试先答:
Iterable提供迭代;Collection下有List/Set/Queue;Map独立于 Collection,常见实现有 HashMap、TreeMap、ConcurrentHashMap。详细解释: List 有序可重复,Set 通常去重,Queue 表示排队,Map 保存键值映射。抽象类如 AbstractList、AbstractMap 复用骨架实现。
示例或流程:
ArrayList、LinkedList实现 List;HashSet基于 HashMap;ArrayDeque实现 Deque。常见追问与易错点: Map 不是 Collection 子接口;实现类的线程安全、排序和 null 规则不同。
2. Collection和Map的区别是什么?
面试先答: Collection 表示一组元素,核心操作是添加、删除、遍历;Map 表示 key-value 映射,核心操作是按 key 查值。
详细解释: Map 的
entrySet/keySet/values可视图化遍历;Map key 通常要求稳定的 equals/hashCode。Collection 的泛型描述元素类型,Map 描述键和值两种类型。示例或流程:
for (Map.Entry<K,V> e : map.entrySet())同时访问键和值。常见追问与易错点: Map 的 values 可重复,key 通常唯一;修改 key 的哈希字段会破坏查找。
3. List、Set、Map、Queue的核心区别是什么?
面试先答: List 保序可重复并支持下标;Set 强调唯一;Map 做键值映射;Queue/Deque 按先进先出或双端规则处理元素。
详细解释: 具体实现决定是否排序、是否允许 null、并发语义和复杂度。选择集合应从访问模式、顺序、重复性和线程安全需求出发。
示例或流程: 日志顺序用 List;标签去重用 Set;缓存索引用 Map;生产消费用 BlockingQueue。
常见追问与易错点: LinkedHashMap 可保持插入/访问顺序但仍是 Map;PriorityQueue 不是完全有序遍历。
4. Iterator迭代器的原理是什么?
面试先答: Iterator 封装集合遍历游标,
hasNext/next逐项访问,remove可安全删除当前元素;多数集合用 modCount 做 fail-fast 检测。详细解释: 结构修改后迭代器预期 modCount 不一致会抛 ConcurrentModificationException,但这是尽力而为而非并发安全保证。并发集合提供弱一致或快照迭代器。
示例或流程:
Iterator it=list.iterator(); while(it.hasNext()){ if(... ) it.remove(); }。常见追问与易错点: foreach 删除元素会异常;CopyOnWriteArrayList 迭代器不支持 remove,看到的是创建时快照。
2.2 List相关
1. ArrayList和Array(数组)的区别是什么?
面试先答: 数组长度固定、可存基本类型、访问开销低;ArrayList 可动态扩容、只存引用类型并提供丰富集合 API。
详细解释: 数组协变可能有运行时类型检查;ArrayList 维护 Object[] 和 size,扩容会复制元素。已知固定长度且性能敏感时数组更合适。
示例或流程:
int[]无装箱,ArrayList<Integer>会装箱;可用Arrays.asList做定长视图但不能增删。常见追问与易错点:
Arrays.asList返回的列表 set 可用但 add/remove 不支持;ArrayListsubList是视图而非独立副本。
2. ArrayList的底层实现是什么?扩容机制是怎样的?
面试先答: ArrayList 基于可变 Object[];容量不足时分配更大数组并复制,JDK 8 典型增长约为原容量的 1.5 倍。
详细解释: 默认构造器延迟到首次 add 分配;
ensureCapacity可预估容量,trimToSize可收缩。扩容是 O(n),摊销 add 仍接近 O(1)。示例或流程: 初始空数组 -> 首次添加分配默认容量 -> 超限按增长因子复制。
常见追问与易错点: 容量 capacity 不等于 size;JDK 版本的默认容量细节可能变化,回答应强调“按需扩容、复制数组”。
3. ArrayList的插入删除性能如何?
面试先答: 尾部追加摊销 O(1);中间插入/删除需移动后续元素 O(n);按下标读取 O(1),按值查找 O(n)。
详细解释: 批量
addAll、removeIf可减少重复移动;大量头部操作应使用 ArrayDeque 或其他结构。并发场景 ArrayList 本身不安全。示例或流程: 在索引 0 插入会移动全部元素,尾部删除只修改 size(必要时置 null 帮助 GC)。
常见追问与易错点: “ArrayList 插入 O(1)”只适用于已定位尾部且有容量;
contains依赖 equals。
4. LinkedList的底层实现是什么?插入删除性能如何?
面试先答: LinkedList 是双向链表,节点保存前后指针和元素;已持有节点/迭代器时插删 O(1),按索引定位 O(n)。
详细解释:
addFirst/addLast常数时间;get(index)从较近端遍历。节点对象多、缓存局部性差,实际性能通常不如 ArrayList。示例或流程:
ListIterator.add在当前位置插入无需移动数组。常见追问与易错点: LinkedList 不是所有插入都 O(1),找到位置的成本不能忽略;作为队列通常优先 ArrayDeque。
5. ArrayList和LinkedList的区别是什么?
面试先答: ArrayList 连续数组、随机访问快、内存紧凑;LinkedList 链表、两端操作方便但节点开销和随机访问差。
详细解释: 大多数读多写少场景选 ArrayList;需要频繁两端进出选 ArrayDeque;只有确有中间节点操作且持有迭代器时才考虑 LinkedList。
示例或流程: 分页读取用 ArrayList;栈/队列用
Deque接口隐藏具体实现。常见追问与易错点: LinkedList 并不自动带来更好插入性能;两者都不是线程安全集合。
6. CopyOnWriteArrayList的原理是什么?适用场景?
面试先答: 写操作复制底层数组并在锁保护下替换引用,读操作无锁、迭代器读快照;适合读多写少、允许短暂一致性的场景。
详细解释: 写入成本 O(n) 且会产生内存峰值;迭代期间的修改对当前迭代器不可见。适用于监听器列表、配置快照,不适合高频写和大集合。
示例或流程: 读线程遍历旧数组,写线程复制并更新,读线程无需阻塞。
常见追问与易错点: 不能依赖
size与随后操作的复合原子性;iterator 不支持 remove。
2.3 Set相关
1. HashSet的底层实现是什么?
面试先答: HashSet 内部用 HashMap 存储元素,元素作为 key,固定 PRESENT 作为 value;利用哈希和 equals 去重。
详细解释: 迭代顺序通常不保证;容量、负载因子影响扩容。JDK 8 HashMap 桶过长时可树化,因此 HashSet 最坏查找可改善。
示例或流程:
set.add(x)等价于map.put(x,PRESENT)==null。常见追问与易错点: HashSet 非线程安全;元素的 hashCode/equals 字段放入后应保持稳定。
2. HashSet如何检查元素重复?
面试先答: 先计算 hash 定位桶,再比较 hash 和 equals;只有 hash 相同且 equals 为 true 才认为重复。
详细解释: hash 冲突允许存在多个不同元素;树桶中按 hash、Comparable 等规则查找。add 返回 false 表示已有等价元素。
示例或流程: 自定义类重写 equals 但不重写 hashCode 会导致“重复元素”出现。
常见追问与易错点: equals 相等必须 hash 相等;hash 相等不必 equals 相等。
3. Set要实现排序该怎么办?
面试先答: 需要实时有序用 TreeSet 并提供自然顺序或 Comparator;只需保持插入顺序用 LinkedHashSet;也可将 Set 转 List 后排序。
详细解释: TreeSet 基于红黑树,增删查 O(log n),比较结果为 0 即视为重复。Comparator 应与 equals 语义一致以避免元素被误去重。
示例或流程:
new TreeSet<>(Comparator.comparing(User::getId))。常见追问与易错点: HashSet 本身不提供排序;改变排序字段会破坏 TreeSet 查找。
4. TreeSet和HashSet的区别是什么?
面试先答: HashSet 基于哈希、平均 O(1)、无序;TreeSet 基于红黑树、O(log n)、按 Comparable/Comparator 排序并支持范围查询。
详细解释: HashSet 允许一个 null(取决于实现),TreeSet 通常不能比较 null;TreeSet 的比较器决定唯一性。
示例或流程:
subSet/headSet/tailSet是 TreeSet 的范围视图。常见追问与易错点: TreeSet “去重”依据比较结果,不一定调用 equals;两者都不是并发安全。
2.4 Map相关
1. HashMap和Hashtable的区别是什么?
面试先答: HashMap 非同步、允许一个 null key 和多个 null value;Hashtable 方法同步、不允许 null,属于遗留类。并发场景优先 ConcurrentHashMap。
详细解释: Hashtable 粗粒度锁并发度低,API 设计也较旧;Collections.synchronizedMap 也只是包装锁,复合操作仍需外部同步。
示例或流程:
Map.computeIfAbsent等原子复合操作应使用 ConcurrentHashMap。常见追问与易错点: HashMap “线程不安全”不等于一定死循环(JDK 8 已修正扩容链表问题);不要用 Hashtable 作为新代码默认选择。
2. HashMap的底层实现是什么?JDK1.7和1.8的区别是什么?
面试先答: 都是数组加链表;JDK 8 桶内链表过长可树化为红黑树,节点插入和扩容实现也调整。JDK 7 主要是数组+链表、头插法。
详细解释: JDK 8 Node 数组按
(n-1)&hash定位,树化阈值通常 8、最小树化容量 64;扩容按高低位拆分,避免重新计算全部 hash。版本细节以具体源码为准。示例或流程: 桶冲突 -> 链表查找;长度达到阈值且容量足够 -> treeify。
常见追问与易错点: 红黑树是最坏情况优化,不代表所有桶都是树;默认负载因子约 0.75 是空间与冲突折中。
3. 往HashMap里put一个值的过程是怎样的?
面试先答: 计算 key hash,按数组索引定位;空桶直接放入,否则比较首节点及链表/树节点,找到则覆盖 value,找不到则追加;size 超阈值触发扩容。
详细解释: null key hash 视为 0;首次 put 可能先初始化数组。树桶执行红黑树查找,插入后维护平衡;覆盖已有 key 不增加 size。
示例或流程:
hash -> index -> equals 查找 -> 插入/覆盖 -> size++ -> resize(必要时)。常见追问与易错点: 返回值是旧 value;修改 key 的 hash 字段后无法可靠 get。
4. HashMap的扩容机制是怎样的?
面试先答: 当前 size 超过
capacity*loadFactor时容量通常翻倍,重新分布节点;JDK 8 利用 hash 的高位拆分,节点只留在原索引或加旧容量后的索引。详细解释: 扩容是 O(n) 并可能造成延迟,构造时预估容量可减少次数;树桶在容量变小或节点数低于阈值时可能退化链表。
示例或流程: 旧容量 16、扩为 32,节点根据
hash & 16分到 0/16 两个位置。常见追问与易错点:
size和容量不是一回事;并发扩容不能靠 HashMap 自身保证安全。
5. HashMap为什么要引入红黑树?树化和退化的条件是什么?
面试先答: 防止恶意或集中 hash 冲突使链表查找退化到 O(n);JDK 8 桶长度达到 TREEIFY_THRESHOLD(通常 8)且容量至少 MIN_TREEIFY_CAPACITY(通常 64)才树化,节点较少时可退化。
详细解释: 小容量优先扩容而不是树化,以降低树节点开销;删除后树节点数量低于 UNTREEIFY_THRESHOLD(通常 6)可能转回链表。阈值是实现常量,版本可能调整。
示例或流程: 冲突增多 -> 扩容;容量足够且桶仍长 -> 红黑树。
常见追问与易错点: 红黑树查找 O(log n) 的前提是比较规则稳定;TreeNode 内存大于普通 Node。
6. HashMap在JDK1.7中头插法为什么会出现死循环?1.8为什么改成尾插法?
面试先答: JDK 7 扩容迁移用头插法,多线程同时 resize 可能把链表指针改成环,get 时死循环;JDK 8 采用尾部保持顺序的拆分迁移,降低该风险,但 HashMap 仍非线程安全。
详细解释: 根因是无同步下共享 table 和 next 指针同时修改,不是“头插法单线程必然出错”。JDK 8 的尾插并不提供可见性或复合操作原子性。
示例或流程: 两线程同时 transfer -> A/B 交叉修改 next -> 环形链表;JDK 8 改为 lo/hi 链表分别链接。
常见追问与易错点: 不能据此说 JDK 8 HashMap 可并发使用;需要 ConcurrentHashMap 或外部锁。
7. HashMap多线程操作为什么不安全?会出现什么问题?
面试先答: 读写缺少同步和 happens-before,可能丢数据、覆盖更新、读到不一致状态;复合操作如 check-then-act 非原子。
详细解释: 扩容、链表/树修改和 size 更新都可能竞态;即使只读,也要保证安全发布且期间无写入。使用 ConcurrentHashMap、不可变快照或锁保护。
示例或流程: 两线程同时
put不同 key,可能 size 不准确或一个更新丢失。常见追问与易错点:
Collections.synchronizedMap的迭代仍需在 map 锁上同步;ConcurrentHashMap 不允许 null 是为了区分缺失与 null 值。
8. HashMap的hash()方法原理是什么?如何解决哈希冲突?
面试先答: JDK 8 将 key.hashCode 的高位与低位异或扰动,再用
(n-1)&hash定位;冲突通过链表或红黑树解决。详细解释: 取模要求除法,容量保持 2 的幂可用位运算;扰动让高位信息参与低位索引。好的 hashCode 应分布均匀且与 equals 一致。
示例或流程:
h ^ (h >>> 16)后与表长减一相与。常见追问与易错点: 只看
hashCode不足以判断相等;容量不是 2 的幂会降低实现优势。
9. HashMap的遍历方式有哪些?
面试先答: 推荐
entrySet遍历键值;只要 key 用keySet,只要 value 用values;可用 Iterator 删除,Java 8 可用 forEach/Stream。详细解释:
entrySet避免每项再次 get;并发修改会触发 fail-fast。需要排序先转 List 或使用 TreeMap/LinkedHashMap。示例或流程:
for (var e: map.entrySet()) { ... }(Java 10 var;Java 8 用显式类型)。常见追问与易错点: Stream 不自动保证线程安全;遍历期间修改要用 iterator.remove 或收集后再改。
10. TreeMap和HashMap的区别是什么?
面试先答: HashMap 哈希平均 O(1)、无序;TreeMap 红黑树 O(log n)、按 key 排序并支持范围导航。
详细解释: TreeMap 的 Comparator/自然顺序决定 key 唯一性;HashMap 允许 null key,TreeMap 通常要求 key 可比较。两者默认均非线程安全。
示例或流程:
floorEntry/ceilingEntry适合区间查找。常见追问与易错点: TreeMap 比较器返回 0 会覆盖已有 key;不要假设 HashMap 迭代顺序。
11. HashMap和ConcurrentHashMap的区别是什么?
面试先答: HashMap 非线程安全且允许 null;ConcurrentHashMap 支持并发读写、禁止 null,并提供原子复合操作和弱一致迭代。
详细解释: CHM 通过 volatile、CAS、桶级 synchronized/树桶等降低锁粒度;读通常无锁,扩容可协作。它不保证全局快照或 size 在瞬间绝对准确。
示例或流程:
computeIfAbsent在并发下原子执行映射函数(函数本身应短且无递归更新)。常见追问与易错点: 并发安全不等于业务事务安全;不要在 mapping function 中长时间阻塞。
12. ConcurrentHashMap的底层实现是什么?JDK1.7和1.8的区别是什么?
面试先答: JDK 7 采用 Segment 分段锁,每段含 HashEntry 数组;JDK 8 去掉 Segment,使用 Node 数组、CAS 和桶级 synchronized,冲突严重时树化。
详细解释: JDK 8 通过 sizeCtl、transferIndex 等协调初始化和扩容;读路径依赖 volatile 可见性,写路径只锁定目标桶。具体字段属于实现细节。
示例或流程: 空桶 CAS 放入;非空桶 synchronized 锁首节点;扩容线程共同迁移。
常见追问与易错点: synchronized 锁的是桶节点而非整个 map;迭代器弱一致,不会抛 fail-fast。
13. ConcurrentHashMap如何实现线程安全?
面试先答: volatile 保证节点和数组可见,CAS 处理空桶竞争,桶级 synchronized 保护冲突更新,计数器用分段累加;扩容支持多线程协作。
详细解释: 读操作按内存语义读取稳定节点;链表/红黑树修改在锁内完成。原子方法如
putIfAbsent/compute将检查和更新组合起来。示例或流程: 两线程写不同桶可并行,同桶则按桶锁串行。
常见追问与易错点:
size()是近似快照;复合业务仍需事务或外部锁。
14. ConcurrentHashMap有哪些原子性复合操作方法?
面试先答:
putIfAbsent、remove(key,value)、replace、computeIfAbsent、computeIfPresent、compute、merge等将条件判断和更新原子化。详细解释: 原子性只覆盖 map 内部这一操作,映射函数应无副作用、快速且不能递归修改同一 map。返回值帮助判断是否发生更新。
示例或流程:
map.merge(k,1,Integer::sum)实现并发计数。常见追问与易错点: 不允许 null key/value;不要用
get后put替代putIfAbsent。
2.5 队列相关
1. Queue和Deque的区别是什么?
面试先答: Queue 通常单端入队、另一端出队;Deque 支持两端插入和删除,可同时充当队列和栈。
详细解释:
offer/poll/peek失败返回特殊值,add/remove/element失败抛异常;ArrayDeque 是无界非线程安全双端队列。示例或流程: FIFO 使用
offerLast/pollFirst,栈使用push/pop。常见追问与易错点: Deque 不允许 null;需要阻塞语义应使用 BlockingQueue。
2. 队列和栈的区别是什么?常用实现类有哪些?
面试先答: 队列 FIFO,栈 LIFO;现代 Java 推荐 Deque/ArrayDeque 实现栈,队列可用 ArrayDeque、LinkedList 或并发队列。
详细解释: ArrayDeque 基于循环数组,头尾操作快;PriorityQueue 按优先级而非 FIFO;Stack 继承 Vector,遗留且同步开销大。
示例或流程: BFS 使用 Queue,DFS/括号匹配使用 Deque。
常见追问与易错点: PriorityQueue 的 iterator 不保证排序;栈溢出与数据结构 Stack 不是同一概念。
3. 阻塞队列的常用实现类有哪些?分别有什么特点?
面试先答: ArrayBlockingQueue 有界数组、可选公平锁;LinkedBlockingQueue 链表可选容量;SynchronousQueue 不存储元素、直接移交;PriorityBlockingQueue 无界优先级;DelayQueue 按延迟出队。
详细解释:
put/take在满/空时阻塞,offer/poll可带超时。生产者消费者模型中应设置容量和拒绝/降级策略,避免无限堆积。示例或流程: 生产者
put-> 队列;消费者take-> 处理;关闭时用哨兵或显式生命周期。常见追问与易错点: LinkedBlockingQueue 默认近似无界,可能导致内存压力;PriorityBlockingQueue 不保证相同优先级顺序。
三、Java并发编程
3.1 线程基础
1. 进程和线程的区别是什么?
面试先答: 进程是资源分配和隔离单位,线程是进程内的执行单元,共享进程资源;线程创建和切换通常比进程轻量,但共享状态带来同步问题。
详细解释: 进程有独立地址空间,线程共享堆、方法区和文件描述符但有独立栈、程序计数器;Java 线程映射到操作系统原生线程(虚拟线程除外)。
示例或流程: 一个 Spring 进程可包含请求线程、GC 线程和定时任务线程。
常见追问与易错点: 共享堆不代表无需同步;Java 21 虚拟线程由 JVM 调度,仍遵守 Java 内存模型。
2. 进程间的通信方式有哪些?
面试先答: 管道/FIFO、消息队列、共享内存、信号量、信号以及 Socket;跨主机主要用网络协议或消息中间件。
详细解释: 共享内存最快但需同步;管道适合亲缘进程流式传输;Socket 通用但有序列化和网络开销。Java 常见实现是 TCP/Unix Socket、文件、数据库和 MQ。
示例或流程: 服务进程通过本机 Socket 或 Redis stream 传递事件。
常见追问与易错点: 信号适合通知而非传输大量数据;跨进程共享文件要处理并发、崩溃恢复和权限。
3. 线程间的通信方式有哪些?
面试先答: 共享变量配合 synchronized/Lock/volatile/原子类,
wait/notify,阻塞队列,CountDownLatch、Future/CompletableFuture 等。详细解释: 通信必须同时满足可见性和协调顺序;高层并发工具优先于手写 wait/notify。条件等待应使用 while 循环防止虚假唤醒。
示例或流程: 生产者
put、消费者take通过 BlockingQueue 传递对象。常见追问与易错点:
sleep不释放锁;仅 volatile 不能保证复合操作原子性。
4. 线程之间哪些内存是共享的?
面试先答: 同一进程的堆、方法区/元空间、静态变量和打开的资源通常共享;每个线程独有栈、程序计数器和 ThreadLocalMap。
详细解释: 共享不等于可见,需通过 happens-before 建立同步;线程栈中保存的引用仍可能指向共享堆对象。线程本地分配缓冲区(TLAB)只是分配优化。
示例或流程:
static int count被所有线程访问,必须原子更新或加锁。常见追问与易错点: JMM 描述抽象内存,不保证物理缓存布局;ThreadLocal 值存在线程对象内部,不是进程全局。
5. 创建线程的方式有哪些?本质上的实现方式是什么?
面试先答: 可继承 Thread、实现 Runnable、实现 Callable 并交给执行器,或使用 CompletableFuture;本质是创建/复用 JVM 线程执行 Runnable,底层通常映射 OS 线程。
详细解释:
new Thread(...).start()才启动新线程,直接调用 run 只是普通方法;线程池复用工作线程。Java 21 可用Thread.ofVirtual()创建虚拟线程。示例或流程: 提交任务 -> Executor 入队/创建 worker -> worker 调用 task.run。
常见追问与易错点: 不要为每个请求无限 new Thread;Callable 的异常和返回值通过 Future 获取。
6. 继承Thread类和实现Runnable接口的核心区别是什么?
面试先答: 继承 Thread 绑定了线程对象且受单继承限制;实现 Runnable 分离任务与执行器,可复用、组合并交给线程池。
详细解释: Runnable 无返回值,Callable 有返回值和受检异常;现代代码通常定义 Runnable/Callable,再由 Executor 管理生命周期。
示例或流程:
executor.submit(() -> work())比自定义 Thread 更易控制资源。常见追问与易错点: Runnable 不是线程;同一个 Runnable 可被多个线程执行,需保证状态安全。
7. Runnable和Callable的区别是什么?
面试先答: Runnable 的
run无返回值且不能抛受检异常;Callable 的call返回结果并可抛异常,通常通过 Future 获取。详细解释:
submit(Callable)返回 Future,可取消、超时和查询异常;execute(Runnable)的异常交给线程的 UncaughtExceptionHandler。示例或流程:
Future<Integer> f=pool.submit(() -> 1+1); f.get()。常见追问与易错点:
get会阻塞;不要在公共线程池中无限等待依赖任务造成饥饿。
8. 线程的生命周期有哪些状态?状态之间如何切换?
面试先答: NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED;start 后进入 RUNNABLE,竞争锁进入 BLOCKED,等待条件进入 WAITING/TIMED_WAITING,run 结束进入 TERMINATED。
详细解释: Java 的 RUNNABLE 包含就绪和运行;
wait/join进入 WAITING,带超时进入 TIMED_WAITING;拿到 monitor 后从 BLOCKED 回到 RUNNABLE。示例或流程:
NEW -> start -> RUNNABLE -> BLOCKED -> RUNNABLE -> TERMINATED。常见追问与易错点: WAITING 不代表线程死了;
Thread.getState是瞬时观测,不能作为精确同步手段。
9. 线程WAITING状态和TIMED_WAITING状态的核心区别是什么?
面试先答: WAITING 无限期等待其他线程唤醒;TIMED_WAITING 带超时,时间到后自动恢复竞争。
详细解释:
Object.wait()、Thread.join()、LockSupport.park()可产生 WAITING;sleep、带超时 wait/join/park 产生 TIMED_WAITING。两者都不占用 CPU 执行。示例或流程:
wait()需 notify/notifyAll;wait(1000)最多等 1 秒。常见追问与易错点: 超时返回不代表条件满足,必须重新检查条件;sleep 不释放 monitor。
10. 什么是线程上下文切换?触发场景有哪些?有什么性能影响?
面试先答: CPU 从一个线程切到另一个线程,需保存/恢复寄存器和栈、污染缓存;阻塞、时间片耗尽、优先级调度和中断都可触发,过多会降低吞吐。
详细解释: OS 调度器负责平台线程切换;虚拟线程切换由 JVM 调度,成本低但阻塞外部调用仍可能占用载体线程。锁竞争和线程数过多会增加切换。
示例或流程: 线程 A 等待 IO -> 调度线程 B -> A 就绪后再恢复。
常见追问与易错点: 并发度不是越高越好;用线程池、批处理和非阻塞 IO 控制切换。
11. sleep()和wait()的区别是什么?
面试先答: sleep 是 Thread 静态方法,暂停指定时间且不释放锁;wait 是 Object 方法,必须持有对象监视器,释放锁并等待通知/超时。
详细解释: wait 被唤醒后要重新竞争锁;sleep 可被中断并抛 InterruptedException。条件同步用 wait/notify 或 Condition,不能用 sleep 轮询代替。
示例或流程:
synchronized(lock){ while(!ok) lock.wait(); }。常见追问与易错点: wait/notify 必须在同一锁对象上调用;处理中断应恢复中断标志或向上抛出。
12. 并发和并行的区别是什么?
面试先答: 并发是多个任务在时间上交错推进,强调结构;并行是多个任务同一时刻在不同 CPU 核执行,强调资源。
详细解释: 单核也能并发但不能真正并行;异步 IO 可提高并发而不增加 CPU 并行度。线程池大小应结合 CPU、IO 和外部依赖。
示例或流程: 单核时间片轮转是并发,多核同时计算是并行。
常见追问与易错点: “多线程等于并行”不准确;并行任务仍需同步共享数据。
13. 同步和异步的区别是什么?
面试先答: 同步调用方等待结果后继续;异步提交后立即返回,由回调、Future 或消息通知结果。它与并发/并行是不同维度。
详细解释: 异步不必然多线程,例如事件循环;同步也可在多线程中实现。选择取决于延迟、吞吐和调用链复杂度。
示例或流程:
CompletableFuture.thenApply形成异步流水线。常见追问与易错点: 异步错误不能被普通 try-catch 直接捕获,需在 stage 中处理;最终仍可能调用 join 等待。
14. 单核CPU支持Java多线程吗?
面试先答: 支持;多个线程通过时间片轮转实现并发,但同一时刻只有一个线程执行,线程切换和同步会带来额外开销。
详细解释: IO 阻塞时切换可提高 CPU 利用率;CPU 密集型线程过多则只增加切换。虚拟线程也可在单核上并发等待 IO。
示例或流程: 一个线程等待磁盘,调度器运行另一个就绪线程。
常见追问与易错点: 多线程不能突破单核计算吞吐上限;并发仍需解决可见性和竞态。
15. Java的线程调度方式有哪些?
面试先答: 平台线程由操作系统抢占式调度,Java 的优先级只是提示;
yield、sleep、join 等影响调度但不保证顺序。详细解释: JVM 负责创建和协调线程,具体时间片、优先级策略依赖 OS;虚拟线程由 JVM 调度器挂载到载体线程。业务不应依赖调度顺序。
示例或流程:
Executor通过队列和 worker 间接控制任务调度。常见追问与易错点:
yield可能被忽略;使用锁、条件、Future 明确表达依赖。
16. 什么是协程?和线程的区别是什么?
面试先答: 协程是用户态可挂起、恢复的执行单元,切换成本低;线程由 OS/JVM 调度、拥有独立栈,资源更重。Java 21 虚拟线程提供类似轻量并发,但不是语言协程。
详细解释: 协程通常在单/少数线程上通过结构化并发运行,需库支持(如 Kotlin);虚拟线程每个任务一个轻量 Thread,阻塞 JDK IO 时可卸载载体线程。
示例或流程: Kotlin
suspend或 JavaExecutors.newVirtualThreadPerTaskExecutor()。常见追问与易错点: 虚拟线程不适合长时间 CPU 密集计算;ThreadLocal、同步块和 pinning 会影响虚拟线程伸缩性。
3.2 线程池
1. 什么是线程池?为什么要使用线程池?
面试先答: 线程池预创建/复用线程并管理任务队列,减少创建销毁成本,控制并发度并提供拒绝、监控和优雅关闭能力。
详细解释: 线程池把任务提交与执行解耦,可隔离不同业务资源;核心是有界队列、合理线程数和明确拒绝策略。IO 线程池应与 CPU 线程池分离。
示例或流程: 提交任务 -> 入队或创建 worker -> 执行 -> 空闲回收。
常见追问与易错点: 线程池不是越大越好;不设置队列上限可能掩盖过载并最终 OOM。
2. 线程池的核心参数有哪些?分别是什么含义?
面试先答: corePoolSize、maximumPoolSize、keepAliveTime、workQueue、threadFactory、RejectedExecutionHandler;还包括 allowCoreThreadTimeOut 等配置。
详细解释: 核心线程优先保留;队列满后才创建非核心线程到 maximum;再满则拒绝。线程工厂负责命名、优先级和未捕获异常处理。
示例或流程:
ThreadPoolExecutor(core,max,keepAlive,unit,boundedQueue,threadFactory,handler)。常见追问与易错点: 实际创建顺序是先到 core、再入队、再扩到 max;有界队列容量应结合吞吐和延迟预算。
3. 线程池的任务执行流程是怎样的?
面试先答: worker 数小于 core 则建核心线程;否则尝试入队;队列满且 worker 小于 max 则建非核心线程;仍无法接收则执行拒绝策略。
详细解释: Worker 通过 getTask 从队列取任务,退出时更新 workerCount;异常退出的 worker 可能被补充。关闭分为不再接收新任务和立即中断。
示例或流程:
execute与submit共享调度流程,submit 额外包装 FutureTask。常见追问与易错点:
shutdown不会立即杀死已执行任务;shutdownNow只是尝试中断并返回未执行任务。
4. 线程池有哪些拒绝策略?分别是什么含义?
面试先答: AbortPolicy 抛异常;CallerRunsPolicy 由提交线程执行;DiscardPolicy 静默丢弃;DiscardOldestPolicy 丢队头再重试。也可自定义降级/告警策略。
详细解释: 策略要匹配业务可靠性:不能丢的任务应持久化或重试,允许降级的可 CallerRuns。拒绝时应记录指标和上下文。
示例或流程: Web 线程触发 CallerRuns 会增加请求延迟但提供背压。
常见追问与易错点: CallerRuns 可能阻塞关键提交线程;Discard 策略绝不能用于有副作用的订单任务。
5. 常见的线程池有哪些?使用Executors创建线程池有什么坑?
面试先答: Fixed、Cached、Single、Scheduled、WorkStealing,以及 Java 21 虚拟线程执行器;Executors 工厂常使用无界队列或无限线程,生产环境容易 OOM/过载。
详细解释:
newFixedThreadPool使用无界 LinkedBlockingQueue;newCachedThreadPoolmaximum 很大;newSingleThreadExecutor适合串行任务;应直接构造 ThreadPoolExecutor 并设置有界容量和监控。示例或流程: 定时任务用 ScheduledThreadPoolExecutor,注意周期任务异常会停止后续执行。
常见追问与易错点: ForkJoinPool.commonPool 是共享资源;虚拟线程不等于无限 CPU 并行。
6. 线程池参数可以动态调整吗?通过什么方式?
面试先答: ThreadPoolExecutor 提供 setCorePoolSize、setMaximumPoolSize、setKeepAliveTime 等 setter,可结合监控动态调整;队列容量通常不能安全随意修改。
详细解释: 调参应有上下限、冷却时间和回滚,观察队列长度、活跃线程、拒绝数和延迟;核心线程数下调时空闲线程才逐步退出。
示例或流程: 配置中心变更 -> 校验范围 -> setter -> 指标观察。
常见追问与易错点: 动态扩容不能解决下游瓶颈;修改最大线程数前确保 core 不大于 max。
7. 线程池任务执行完后线程如何回收?
面试先答: worker 循环从队列取任务,空闲超过 keepAliveTime 的非核心线程退出;开启 allowCoreThreadTimeOut 后核心线程也可回收;关闭线程池后全部退出。
详细解释: getTask 超时返回 null 触发 worker 退出,WorkerCount 原子更新;任务 finally 应清理 ThreadLocal。线程池本身若不关闭会阻止 JVM 非守护线程退出。
示例或流程:
try { task.run(); } finally { cleanup(); }。常见追问与易错点: 线程退出不等于任务被取消;中断只是一种协作信号。
8. 线程池中线程异常后,会被销毁还是复用?
面试先答: execute 提交的任务若抛未捕获异常,worker 通常结束并由线程池补充;submit 将异常封装进 Future,worker 可继续复用,需 get 才能看到。
详细解释: ThreadPoolExecutor.runWorker 的 finally 负责回收 worker;设置 UncaughtExceptionHandler 可记录 execute 异常。业务任务应自行捕获并分类处理。
示例或流程:
Future.get()抛 ExecutionException,根因在getCause()。常见追问与易错点: 不要依赖日志自动发现 submit 异常;任务 finally 释放资源和清理上下文。
9. 如何给线程池命名?有什么作用?
面试先答: 自定义 ThreadFactory 设置有意义的前缀、编号、守护属性和异常处理;命名便于日志、jstack、监控和故障定位。
详细解释: 可使用 Guava ThreadFactoryBuilder 或手写原子计数器;不同业务线程池应使用不同前缀,避免共享池混淆。
示例或流程:
order-worker-%d、io-worker-%d。常见追问与易错点: 不要默认把业务线程设为 daemon,否则 JVM 退出时任务可能丢失。
10. 线程池大小如何规划配置?CPU密集型和IO密集型如何考量?
面试先答: CPU 密集通常接近 CPU 核数或核数+1;IO 密集可按
核数 * (1 + 等待时间/计算时间)估算,再用压测和下游容量校正。详细解释: 线程数受 CPU、连接池、队列、内存和 SLA 共同约束;监控运行队列、上下文切换、拒绝率和外部延迟。虚拟线程适合大量阻塞 IO,但下游并发仍需限流。
示例或流程: 4 核、平均 80% 时间等待 IO,可从约 20 个平台线程起步再压测。
常见追问与易错点: 公式只是起点;不要让线程池大于数据库连接池几十倍造成排队和超时。
11. 什么是Future类?有什么作用?
面试先答: Future 表示异步计算结果,可查询完成、阻塞获取、取消和判断取消状态。
详细解释:
get(timeout)防止无限等待;取消是中断请求,任务必须响应中断。Future API 组合能力弱,异常和多个任务编排较繁琐。示例或流程: 提交任务 -> 返回 Future -> get/取消/超时。
常见追问与易错点:
isDone只表示结束不代表成功;不要在公共线程池中互相 get 造成死锁。
12. CompletableFuture相比Future有什么优势?
面试先答: CompletableFuture 支持链式组合、回调、异常处理、并行聚合和显式线程池,减少阻塞等待。
详细解释:
thenApply/thenCompose/allOf/anyOf/handle表达依赖关系;默认使用 ForkJoinPool.commonPool,生产环境常传入自定义 Executor。异常会沿 stage 传播。示例或流程:
a.thenCombine(b,(x,y)->x+y).exceptionally(...)。常见追问与易错点:
allOf不直接返回结果,需逐个 join;异步 stage 中的阻塞调用会占满执行器。
13. 一个任务依赖另外两个任务才能执行,该怎么设计?
面试先答: 用 CompletableFuture 并行启动两个任务,再用
thenCombine/allOf汇合;明确超时、异常和取消策略。详细解释: 两个独立任务共享同一受控线程池,任一失败决定是否短路;汇合阶段只在两个结果可用后执行。也可用 CountDownLatch,但结果传递和异常处理不如 CF 直观。
示例或流程:
f1.thenCombine(f2,(r1,r2)->merge(r1,r2))。常见追问与易错点: 不要让 f1/f2 在同一单线程池中互相等待;设置
orTimeout或completeOnTimeout。
3.3 锁机制
1. 什么是死锁?死锁的四个必要条件是什么?
面试先答: 死锁是线程互相等待永远无法继续;必要条件为互斥、占有且等待、不可剥夺、循环等待。
详细解释: 多把锁按不同顺序获取最容易形成环;数据库锁、线程池任务依赖也可能造成广义死锁。破坏任一条件即可避免。
示例或流程: T1 持 A 等 B,T2 持 B 等 A。
常见追问与易错点: 活锁会不断重试但无进展,饥饿是某线程长期得不到资源,概念不同。
2. 如何检测死锁?如何避免死锁?
面试先答: 用 jstack、
ThreadMXBean.findDeadlockedThreads、JFR/监控检测;通过统一锁顺序、超时 tryLock、减少锁粒度和避免嵌套锁预防。详细解释: 线上先保留线程转储和资源信息,再确认是否有环;tryLock 超时后应回滚已获取锁并重试/降级。锁顺序可按对象唯一 ID 排序。
示例或流程:
try { if(lockA.tryLock(t)) ... } finally { unlock }。常见追问与易错点: jstack 只反映采样时刻;不要为了避免死锁直接去掉所有同步。
3. 什么是悲观锁?什么是乐观锁?分别的适用场景?
面试先答: 悲观锁假设冲突频繁,先独占再操作(synchronized/数据库 for update);乐观锁先读版本,提交时校验冲突(CAS/version),适合读多写少。
详细解释: 悲观锁阻塞并发但逻辑简单;乐观锁冲突时重试,吞吐高但需幂等和重试上限。数据库隔离级别也影响锁行为。
示例或流程:
UPDATE ... WHERE id=? AND version=?成功行数为 1 才提交。常见追问与易错点: 乐观锁不是“完全不加锁”;CAS 只适合短小原子状态,不适合复杂事务。
4. 如何实现乐观锁?
面试先答: 使用版本号/时间戳条件更新、CAS 原子变量或 compare-and-set;失败则重读并按策略重试/返回冲突。
详细解释: SQL 更新必须把版本条件放在 where,并在成功后 version+1;缓存/分布式场景要注意时钟、幂等和 ABA。重试需限制次数和退避。
示例或流程:
UPDATE account SET balance=?,version=version+1 WHERE id=? AND version=?。常见追问与易错点: 时间戳可能精度不足;只在 Java 内存中 CAS 无法保护数据库多实例写入。
5. 什么是CAS?CAS的原理是什么?
面试先答: CAS 原子比较并交换:只有内存值等于预期值才写入新值,否则失败;依赖 CPU 原子指令和 volatile 可见性。
详细解释:
AtomicInteger循环读取当前值并 CAS 更新;无锁但失败会自旋。Unsafe/VarHandle 提供底层接口。示例或流程:
do { old=v; next=f(old); } while(!cas(old,next));。常见追问与易错点: CAS 不能保证多个变量一致性;高竞争下自旋浪费 CPU。
6. CAS的ABA问题是什么?如何解决?
面试先答: 值从 A 变 B 又变回 A,CAS 只比较值会误认为未变化;用带版本号的 AtomicStampedReference/自增标记或不可变节点解决。
详细解释: 无锁栈、对象复用中 ABA 可能导致丢失中间更新;版本溢出和引用生命周期也要考虑。AtomicMarkableReference 适合布尔标记。
示例或流程: 比较
(value,stamp)而非单一 value。常见追问与易错点: ABA 不等于数据一定错误,是否有影响取决于算法不变量。
7. CAS有哪些局限性?
面试先答: 只能保护单一位置,失败会自旋,存在 ABA,竞争激烈时 CPU 开销高,且难表达复杂事务。
详细解释: 可用锁、分段计数器、退避和更高层并发结构缓解;LongAdder 通过分散热点提高高竞争计数吞吐,但读取不是严格瞬时值。
示例或流程: 计数器高并发用 LongAdder,必须精确快照时用锁或 AtomicLong。
常见追问与易错点: “无锁”不代表无等待;内存序和可见性仍需正确处理。
8. synchronized关键字的作用是什么?怎么使用?
面试先答: synchronized 提供互斥和内存可见性,可修饰实例方法、静态方法或代码块;退出监视器时释放锁并建立 happens-before。
详细解释: 实例锁是 this,静态锁是 Class 对象,代码块可选择私有锁对象。锁保护的范围应覆盖完整不变式,避免锁住外部可控对象。
示例或流程:
synchronized(lock){ balance += amount; }。常见追问与易错点: synchronized 方法锁的是对象而非方法;不同实例之间实例锁互不影响。
9. 构造方法能加synchronized吗?
面试先答: 语法上不能直接声明 synchronized 构造器;构造期间对象尚未对外发布,通常不需要对象锁。若访问共享资源,可在构造体内同步。
详细解释: 构造器可调用同步方法但此时对象可能未安全发布;更应避免在构造器中启动线程或泄露 this。静态初始化可用类初始化安全保证。
示例或流程:
public Foo(){ synchronized(lock){...} }合法,但public synchronized Foo()非法。常见追问与易错点: synchronized 不能防止其他线程拿到未完成构造的引用;发布应通过 final、锁、volatile 或并发容器。
10. synchronized的底层原理是什么?
面试先答: 编译为 monitorenter/monitorexit(方法可由 ACC_SYNCHRONIZED 标志表示),JVM 通过对象监视器实现互斥、重入和内存屏障。
详细解释: HotSpot 会使用轻量级/重量级路径和自适应自旋;锁记录、对象头 mark word 是实现细节。JDK 15 后偏向锁默认关闭,锁形态不能简单背成固定升级链。
示例或流程: 进入 monitor -> 竞争/阻塞或自旋 -> 执行 -> finally monitorexit。
常见追问与易错点: synchronized 可重入;异常退出也必须释放锁。
11. synchronized的锁升级过程是什么?
面试先答: 早期 HotSpot 常讲无锁→偏向→轻量级→重量级,但具体版本实现会变化;JDK 15 默认关闭偏向锁,现代回答应以“竞争程度选择优化路径”为准。
详细解释: 轻量路径通过 CAS 锁记录并短暂自旋,竞争激烈时膨胀为 ObjectMonitor 阻塞;偏向锁在 JDK 15 禁用、JDK 18 移除相关实验选项。不要把升级视为永久单向过程。
示例或流程: 无竞争快速路径 -> 短竞争自旋 -> 长竞争阻塞。
常见追问与易错点: 锁粗化、锁消除是 JIT 优化,不等同于对象锁升级;JDK 版本差异必须说明。
12. volatile关键字的作用是什么?
面试先答: volatile 保证变量读写可见、禁止相关指令重排序,并提供单次读写原子性;不能保证
i++等复合操作原子。详细解释: 写 volatile 与后续读建立 happens-before;适合状态标志、发布不可变引用和单写多读场景。JMM 不要求每次都刷新物理内存,保证的是抽象语义。
示例或流程:
volatile boolean stopped让工作线程及时看到停止信号。常见追问与易错点: volatile 不保证互斥和复合不变式;long/double 在现代 JVM 普遍原子但仍应按语义使用 volatile。
13. volatile如何保证可见性和禁止指令重排序?
面试先答: 编译器和 CPU 对 volatile 读写插入内存屏障,写入先行于后续读观察,阻止跨越屏障的重排序。
详细解释: volatile 写具有 release 语义,读具有 acquire 语义;具体屏障指令依 CPU 架构。它只约束与该变量相关的内存操作,不能自动保护其他竞态。
示例或流程: 发布
config前先构造对象,再 volatile 写引用;读线程 volatile 读后看到初始化状态。常见追问与易错点: 双重检查单例必须对实例引用加 volatile;仅把字段声明 volatile 不能修复非原子复合逻辑。
14. 为什么volatile不能保证原子性?
面试先答:
i++包含读、加、写三步,即使每步可见,多个线程仍可能交错覆盖;需要 synchronized、Lock 或 AtomicInteger 的 CAS。详细解释: volatile 适合单次状态读写,不适合维护多个字段的一致性。原子类提供针对单变量的线性化操作。
示例或流程: 两线程都读 0,各写 1,结果丢失一次更新。
常见追问与易错点:
volatile long的单次读写原子不等于自增原子;AtomicLong 也不能同时更新两个字段。
15. volatile和synchronized的区别是什么?
面试先答: volatile 无锁、保证可见性和有序性但不保证复合原子;synchronized 提供互斥、可见性、可重入和条件等待,代价是竞争阻塞。
详细解释: volatile 适合状态标志/发布引用;synchronized 适合保护不变式和多步操作。二者可组合,例如 volatile 快速检查、锁内完成更新。
示例或流程: 停止标志 volatile,账户余额转账 synchronized。
常见追问与易错点: synchronized 现代 JVM 快速路径性能已很好,不应为了“无锁”滥用 volatile。
16. ReentrantLock是什么?和synchronized的区别是什么?
面试先答: ReentrantLock 是基于 AQS 的可重入显式锁,支持公平、可中断、超时 tryLock 和多个 Condition;synchronized 语法更简单且自动释放。
详细解释: lock 必须在 finally unlock;公平锁减少饥饿但吞吐较低。Condition 对应独立等待队列,能实现多条件协作。
示例或流程:
lock.lock(); try{...} finally{lock.unlock();}。常见追问与易错点: 忘记 unlock 会造成死锁;不是所有场景显式锁都优于 synchronized。
17. 公平锁和非公平锁的区别是什么?底层如何实现?
面试先答: 公平锁按等待队列先来先服务,减少饥饿但有排队开销;非公平锁允许插队,吞吐通常更高。ReentrantLock 通过 AQS 队列和 tryAcquire 实现。
详细解释: 非公平锁先 CAS 抢锁,失败后入队;公平锁会先检查队列前驱。公平性只针对锁竞争,不保证线程整体执行顺序。
示例或流程:
new ReentrantLock(true)创建公平锁。常见追问与易错点: synchronized 没有可配置公平性;公平锁也可能因调度产生延迟差异。
18. ReentrantReadWriteLock是什么?适用场景?
面试先答: 读写锁允许多个读者并发、写者独占,适合读多写少且临界区较长的共享数据。
详细解释: 支持锁降级(写锁持有时获取读锁后释放写锁),通常不支持直接升级读到写;公平模式可缓解写饥饿。读写锁本身有维护开销。
示例或流程: 配置缓存读锁读取,刷新时写锁替换快照。
常见追问与易错点: 读多写少但临界区很短时 ReentrantLock/volatile 快照可能更快;升级读锁容易死锁。
19. 什么是AQS?AQS的原理是什么?
面试先答: AQS 是基于 FIFO 等待队列和 volatile state 的同步器框架,子类实现 tryAcquire/tryRelease,支持独占和共享模式。
详细解释: 获取失败的线程入队并通过 LockSupport.park 挂起,释放后唤醒后继;ReentrantLock、Semaphore、CountDownLatch 等都基于 AQS(StampedLock 不直接继承)。
示例或流程: CAS 修改 state -> 失败入队 -> park -> 前驱释放后 unpark -> 重试。
常见追问与易错点: AQS 不是具体锁;state 含义由子类定义,必须正确处理中断和取消节点。
3.4 并发工具类
1. CountDownLatch、CyclicBarrier、Semaphore的区别和使用场景?
面试先答: CountDownLatch 一次性等待计数归零;CyclicBarrier 让一组线程反复相互等待;Semaphore 控制同时访问资源的许可数。
详细解释: Latch 适合主线程等待多个子任务;Barrier 可配置 barrier action;Semaphore 适合连接/并发额度限流。三者实现细节不同但都解决协调而非数据保护。
示例或流程: N 个 worker
countDown,主线程await;每轮 barrier await;借许可 finally release。常见追问与易错点: Latch 不能重置;Semaphore 许可泄漏会导致永久阻塞。
2. ThreadLocal是干嘛的?原理是什么?
面试先答: ThreadLocal 为每个线程提供独立变量副本,避免显式加锁;值存在线程的 ThreadLocalMap 中,ThreadLocal 作为弱引用 key。
详细解释:
get/set通过当前线程查表,哈希冲突用开放寻址;remove 清理值。常用于 traceId、事务上下文、非线程安全格式化器。示例或流程:
try { tl.set(ctx); ... } finally { tl.remove(); }。常见追问与易错点: ThreadLocal 不是跨线程传递工具;在线程池中必须清理,防止上下文串线。
3. ThreadLocal如何实现线程隔离?
面试先答: 每个 Thread 对象有独立 ThreadLocalMap,ThreadLocal 只是访问入口,同一 ThreadLocal 在不同线程映射到不同 value。
详细解释: key 弱引用便于 ThreadLocal 被回收,但 value 可能因 stale entry 留在线程中,访问时会清理部分槽位。线程隔离不代表对象本身线程安全。
示例或流程: 线程 A/B 各自
tl.set,互不读取对方值。常见追问与易错点: InheritableThreadLocal 会复制父线程值,可能造成共享可变对象,不应误当安全传递。
4. ThreadLocal内存泄漏是什么场景触发的?怎么解决?
面试先答: 在线程长期存活(尤其线程池)时,ThreadLocal key 被 GC 后 value 仍可能挂在 Map,形成 stale entry;用完 finally remove,避免大对象和静态 ThreadLocal 滥用。
详细解释: Map 只在 get/set/rehash 时顺带清理,不能依赖及时回收;类加载器热部署时还可能因 value 引用导致泄漏。框架应统一拦截清理。
示例或流程:
try/finally包围请求处理;线程池任务结束清理 MDC、租户信息。常见追问与易错点: key 是弱引用不等于不会泄漏;泄漏是生命周期不匹配,不是 ThreadLocal 必然问题。
5. 线程池场景下使用ThreadLocal会导致什么问题?如何解决?
面试先答: 工作线程复用会让上个任务的值污染下个任务,并长期持有资源;任务 finally 清理,或使用任务包装器/框架上下文传播。
详细解释: 线程池提交者和执行者不同,ThreadLocal 默认不自动传递;对于 traceId 可用显式参数、装饰 Runnable 或 TransmittableThreadLocal。传播对象应不可变或复制。
示例或流程:
try { set(requestId); task.run(); } finally { remove(); }。常见追问与易错点: 只在入口 set 不足以防异常路径泄漏;不要把数据库连接长期放 ThreadLocal。
6. 不同ThreadLocal之间如何进行信息传递?
面试先答: ThreadLocal 之间没有内置传递关系;可显式读取后复制、封装上下文对象,或用 InheritableThreadLocal/上下文传播库,但要注意线程池复用。
详细解释: 最可靠是把上下文作为参数或不可变 Context 传递;异步链可用 CompletableFuture 的显式上下文包装。InheritableThreadLocal 只在线程创建时复制。
示例或流程:
Context ctx=Context.capture(); executor.execute(() -> ctx.run(task));。常见追问与易错点: 不能遍历其他 ThreadLocalMap;反射访问是脆弱且不安全的。
7. TransmittableThreadLocal相比InheritableThreadLocal核心解决了什么问题?
面试先答: InheritableThreadLocal 只在线程创建时继承;TTL 在任务提交/执行时捕获和恢复上下文,适配线程池复用和异步任务。
详细解释: TTL 需要对 Executor/Runnable 做包装,执行后恢复旧值避免污染;传播可变对象仍需复制或只读约束。Java 21 ScopedValue 提供更结构化的只读上下文方案(预览/版本依赖)。
示例或流程: 提交时 capture -> worker replay -> finally restore。
常见追问与易错点: TTL 增加包装和拷贝成本,不能替代业务参数;并非所有第三方异步框架自动支持。
8. Atomic原子类的原理是什么?
面试先答: 基于 volatile 字段和 CAS 提供单变量原子更新;LongAdder 通过分散热点提高高并发累加吞吐。
详细解释: AtomicReference 支持对象引用 CAS,AtomicStampedReference 解决 ABA;VarHandle 是现代底层访问 API。原子类只保证自身操作,不保证跨变量事务。
示例或流程:
incrementAndGet循环 CAS;compareAndSet(expect,update)成功才更新。常见追问与易错点: 复合逻辑要使用 updateFunction 原子方法或锁;LongAdder 的 sum 不是严格线性化快照。
3.5 JMM内存模型
1. 什么是JMM模型?三大特性是什么?如何保证?
面试先答: JMM 规定线程与共享内存的抽象交互,核心特性是原子性、可见性、有序性;通过 synchronized/Lock、volatile、final、原子类和 happens-before 规则保证。
详细解释: happens-before 包括程序次序、锁解锁-加锁、volatile 写-读、线程 start/join 等;它是可见性和排序的推理工具,不是物理执行顺序描述。
示例或流程: 线程 A 解锁后,线程 B 加锁可看到 A 在锁内写入。
常见追问与易错点: 64 位读写原子性和 volatile 语义要区分;没有同步的普通变量可能读到旧值。
2. 什么是指令重排序?如何禁止指令重排序?
面试先答: 编译器、JIT 和 CPU 为性能可在不改变单线程结果的前提下调整指令顺序;volatile、锁和正确的 final 发布插入约束,禁止跨线程可见的错误重排。
详细解释: 双重检查单例中实例引用必须 volatile,防止构造未完成就发布;happens-before 允许线程推导安全顺序。不要用空循环或 println“碰巧”阻止重排。
示例或流程:
volatile Singleton instance保证写引用后构造字段对读线程可见。常见追问与易错点: 重排不是每次都发生;
synchronized同时提供互斥、可见性和顺序。
3. 什么是内存屏障?有什么作用?
面试先答: 内存屏障是编译器/CPU 的排序与可见性约束点,限制读写重排并确保必要数据对其他线程可见;JMM 通过语言语义定义效果。
详细解释: volatile 写常带 release 屏障、读带 acquire 屏障,锁操作还包含更强的屏障;具体指令因 CPU 架构而异。屏障有成本,应使用高层同步原语。
示例或流程: 写屏障前完成对象字段初始化,随后发布 volatile 引用。
常见追问与易错点: 内存屏障不是“刷新所有缓存”的简单说法;不能直接调用屏障替代锁保护复杂状态。
四、JVM虚拟机
4.1 内存模型与内存区域
1. JVM内存模型是怎样的?各区域的作用是什么?
面试先答: 运行时数据区包括程序计数器、虚拟机栈、本地方法栈、堆和方法区;前三者多为线程私有,堆和方法区为线程共享。JMM(并发内存模型)是另一抽象概念,不要混淆。
详细解释: PC 记录当前字节码位置;栈保存栈帧、局部变量和操作数栈;堆放对象;方法区存类元数据、运行时常量池等,HotSpot JDK 8 以后的实现是元空间。直接内存属于 JVM 外部本地内存。
示例或流程: 线程调用方法 -> 创建栈帧;
new对象 -> 堆分配;类首次使用 -> 类加载并进入元空间。常见追问与易错点: “JVM 内存模型”在题目中常指运行时区域,但 JMM 是可见性/有序性规范;方法区不是永久代的同义词。
2. 堆和栈的区别是什么?
面试先答: 堆共享、存放对象、由 GC 管理;栈按线程私有、保存调用栈帧、自动随方法进出,空间小且生命周期短。
详细解释: 栈溢出常由深递归或大栈帧引起,堆 OOM 常由对象无法回收;引用可能在栈中,指向堆对象。JIT 逃逸分析可能消除堆分配。
示例或流程:
foo()入栈创建 frame,返回时 frame 弹出;对象若仍被引用则继续存活。常见追问与易错点: 不要说“基本类型全在栈、对象全在堆”;物理布局由实现和优化决定。
3. 方法区/元空间是什么?存储什么内容?
面试先答: 方法区是规范概念,存类元数据、方法信息、运行时常量池和静态变量等;HotSpot JDK 8 起用本地内存元空间替代永久代。
详细解释: 元空间可通过
-XX:MetaspaceSize/MaxMetaspaceSize控制,类卸载后元数据可释放。字符串常量池在 JDK 7 起主要位于堆,不应与元空间混为一谈。示例或流程: 动态生成大量类会消耗元空间并触发
OutOfMemoryError: Metaspace。常见追问与易错点: 静态字段实现细节随 JDK 变化;元空间使用本地内存但仍受进程/容器限制。
4. 什么是直接内存?有什么作用?
面试先答: 直接内存是堆外本地内存,NIO DirectByteBuffer 可让 IO 直接与内核缓冲区交互,减少堆内外复制;分配和回收需额外成本。
详细解释: 受
-XX:MaxDirectMemorySize和本地内存限制,释放依赖 Cleaner/引用处理;Netty 使用池化 direct buffer。泄漏会表现为进程内存高但堆统计正常。示例或流程: socket -> direct buffer -> channel,避免 byte[] 到 native buffer 的重复拷贝。
常见追问与易错点: 直接内存不是“完全不复制”;不能只看 heap dump 判断其使用量。
5. 哪些区域容易发生OOM?OOM的常见触发场景有哪些?
面试先答: 堆、元空间、直接内存和线程栈都可能 OOM;常见原因是堆对象泄漏/超大缓存、动态类过多、DirectBuffer 泄漏、线程数过多或栈过深。
详细解释: OOM 消息能提示区域;需要结合 heap dump、NMT、线程转储和 GC 日志定位。容器环境还需考虑 cgroup 内存上限和 native 库分配。
示例或流程:
Java heap space看对象占用;unable to create native thread看线程、栈大小和系统限制。常见追问与易错点: Full GC 后仍 OOM 才更像泄漏/存活集过大;增大堆只是缓解,不是根因修复。
6. 堆的作用是什么?结构是怎样的?
面试先答: 堆存放大多数对象和数组,是 GC 主要管理区域;分代收集器通常划分新生代(Eden、Survivor)和老年代。
详细解释: 对象先在 Eden 分配,Minor GC 后存活对象在 Survivor 间复制,达到年龄或大对象阈值后晋升老年代;G1 将堆划为 Region,不是固定连续代区。
示例或流程:
Eden -> S0/S1 复制 -> Old;大对象可能直接进入老年代(取决于收集器)。常见追问与易错点: 新生代比例和晋升年龄是实现参数;ZGC/Shenandoah 的分代特性随 JDK 版本演进。
7. 字符串常量池是什么?有什么作用?为什么放在堆中?
面试先答: 字符串池保存规范化字符串,复用相同字面量;JDK 7 起主要位于堆,便于统一 GC 管理并避免永久代空间受限。
详细解释:
intern将字符串加入/查找池;池对象仍受 GC 影响,只要无引用即可回收。池与运行时常量池相关但不是同一个概念。示例或流程:
"a"、intern()共享池引用,普通new String可独立存在。常见追问与易错点: 不能说“常量池永远不会回收”;大量动态 intern 仍会占堆内存。
8. 对象的创建过程是怎样的?
面试先答: 检查类是否已加载 -> 分配内存(指针碰撞/空闲列表)-> 初始化零值和对象头 -> 设置字段/执行构造器 -> 返回引用;并发分配常用 TLAB。
详细解释: 分配前需确定类元数据和实例大小;初始化顺序包括父类初始化、实例字段默认值、显式初始化和构造器。锁消除/逃逸分析可能优化分配。
示例或流程:
new User():类解析 -> Eden/线程 TLAB 分配 -> 默认值 -><init>。常见追问与易错点: 构造器执行失败对象不会作为完整对象返回;内存分配不等于对象对其他线程安全发布。
9. 线程是如何访问使用对象的?句柄访问和直接指针访问的区别?
面试先答: 线程持有对象引用,通过句柄或直接指针找到堆对象;句柄多一次间接访问但移动对象更稳定,直接指针访问快但 GC 移动时需更新引用。
详细解释: HotSpot 常用直接指针,引用指向对象头,GC 更新根和对象引用;句柄表方案可保持句柄不变。两者是实现选择,Java 代码不可感知。
示例或流程: 引用 ->(句柄表 -> 对象)或 -> 对象地址 -> 字段。
常见追问与易错点: 压缩指针是地址编码优化,不等于句柄;不要把引用本身说成 C++ 裸指针。
4.2 垃圾回收机制
1. Java垃圾回收机制是什么?
面试先答: GC 自动识别不可达对象并回收/整理内存,减少手动释放错误;不同收集器在吞吐、延迟和内存开销间取舍。
详细解释: GC 通过根可达性、标记、复制、清除和整理等算法工作;停顿型与并发型收集器适合不同 SLA。GC 不能保证实时释放,也不能替代关闭文件/连接。
示例或流程: Roots -> 标记存活 -> 回收垃圾 -> 必要时压缩/整理。
常见追问与易错点:
System.gc()只是建议;对象可回收不代表立即回收。
2. 如何判断对象是否可回收?
面试先答: 以 GC Roots 为起点做可达性分析,无法到达的对象才是候选垃圾;比早期引用计数更能处理循环引用。
详细解释: 根包括栈引用、静态字段、JNI 引用和同步锁持有对象等;标记阶段需处理并发快照和写屏障。软/弱/虚引用还受引用处理策略影响。
示例或流程: A 引用 B、B 引用 A,但无 Root 到 A/B,二者都可回收。
常见追问与易错点: finalize(已废弃)可能使对象“复活”,但不应依赖;可达性分析是逻辑判定,不是立即释放。
3. 哪些对象可以作为GC Roots?
面试先答: 虚拟机栈和本地方法栈中的引用、方法区静态字段/常量引用、运行中线程对象、JNI 引用、同步锁持有对象以及 JVM 内部关键对象。
详细解释: 具体根集合由收集器实现决定,类加载器、系统类和 JVMTI 也可能形成根。排查泄漏要从 GC Root 的引用链反向定位业务持有者。
示例或流程: 静态 Map 持有用户对象 -> 对象始终可达 -> 缓存泄漏。
常见追问与易错点: “所有全局变量都是根”过于笼统,必须看是否仍被活跃类加载器/字段引用。
4. 可被回收的对象一定会被回收吗?
面试先答: 不一定立即回收;只有触发 GC 且收集器选择处理时才回收。finalize 旧机制还可能使对象在一次 GC 中复活,但已弃用。
详细解释: JVM 可在内存足够时延迟 GC,也可能因性能策略不回收某些区域。应通过资源显式关闭,而不是等待 GC。
示例或流程: 变为不可达 -> 下一次 GC 发现 -> 清除/复制(时间不确定)。
常见追问与易错点:
System.gc()不是强制命令;Cleaner 也不是确定性资源管理工具。
5. Java的引用类型有哪些?分别是什么含义?
面试先答: 强引用、软引用、弱引用、虚引用;强引用只要存在对象就不能回收,软引用适合内存敏感缓存,弱引用下一次 GC 可回收,虚引用用于跟踪回收和清理。
详细解释: 引用对象通过 ReferenceQueue 接收处理通知;软引用回收策略与堆压力和 JVM 版本有关,不适合严格缓存。虚引用
get()永远返回 null。示例或流程:
WeakHashMap用弱 key;DirectBuffer Cleaner 使用虚引用/清理机制。常见追问与易错点: 弱引用不保证“立即”回收;软引用不能替代有上限的缓存。
6. 如何判断一个类是无用类?
面试先答: 类加载器实例可回收、该类的 Class 对象无强引用、该类实例全部不可达,并且没有反射/同步等内部引用;满足条件时类可卸载。
详细解释: 类卸载通常发生在 Full GC,且仅对自定义类加载器加载的类有意义;Bootstrap 平台类通常长期存在。动态代理、脚本和热部署容易造成类加载器泄漏。
示例或流程: WebAppClassLoader 无引用 -> 其 Class/实例均不可达 -> GC 后卸载元数据。
常见追问与易错点: 类“没有实例”不等于可卸载;静态线程、ThreadLocal、注册表常持有类加载器。
7. 常见的垃圾回收算法有哪些?分别的优缺点和适用场景?
面试先答: 标记-清除不移动但有碎片;复制适合存活率低的新生代;标记-整理减少碎片但停顿较长;分代/分区将算法组合以适配不同区域。
详细解释: 复制需要预留空间,整理需移动对象并更新引用;并发标记/清除降低停顿但有浮动垃圾和写屏障开销。G1/ZGC 使用 Region 和并发/增量整理思路。
示例或流程: Eden 复制存活对象,老年代标记整理。
常见追问与易错点: 没有“最优算法”;要结合存活率、堆大小、暂停目标和吞吐。
8. 分代收集算法的原理是什么?
面试先答: 基于“多数对象朝生夕死”,把堆分新生代和老年代,分别采用复制、标记整理等策略,减少每次扫描全堆成本。
详细解释: 新生代 Minor GC 频繁且快,存活对象晋升;老年代收集更少但代价大。跨代引用用 Remembered Set/卡表跟踪,避免扫描整个老年代。
示例或流程: Eden 分配 -> Minor GC -> Survivor 年龄增长 -> 晋升 Old。
常见追问与易错点: G1 以 Region 管理但仍可分代(JDK 8 主要不分代、后续版本演进);大对象和 humongous 区需特别关注。
9. 堆的GC流程是怎样的?
面试先答: 分配失败或达到阈值触发收集 -> 识别 Roots/跨代引用 -> 标记存活 -> 复制/清除/整理 -> 更新引用和统计 -> 继续分配。
详细解释: Stop-The-World 阶段暂停 Java 线程;并发收集器把标记/清理与应用线程重叠,需写屏障记录变更。晋升失败可能触发 Full GC。
示例或流程: Minor GC 只处理年轻区,老年代阈值或并发周期条件满足时启动 Major/Full。
常见追问与易错点: “GC 只在堆满时发生”错误;分配担保、元空间和显式 GC 也会影响触发。
10. Minor GC、Major GC、Full GC的区别是什么?
面试先答: Minor/Young GC 回收年轻代;Major 常指老年代回收(定义不统一);Full GC 通常覆盖整个堆及相关元数据,停顿更重。
详细解释: 不同收集器术语可能不同,G1 的 mixed GC 同时回收部分老年代和年轻区。判断影响应看实际日志事件和停顿,而非只看名称。
示例或流程: 新生代满 -> Young GC;晋升失败/元空间不足 -> Full GC。
常见追问与易错点: Major 不总是等于 Full;CMS 的 concurrent mode failure 可能退化 Full GC。
11. Full GC的触发机制是什么?
面试先答: 老年代分配失败、晋升失败、元空间不足、显式 GC、担保失败或收集器特定条件都可能触发;需结合 GC 日志确认。
详细解释: System.gc 可被
-XX:+DisableExplicitGC禁用或由收集器处理;G1 在 evacuation failure、并发周期未及时完成时可能 Full。大对象分配也会加速触发。示例或流程: Old 无足够连续空间 -> Full GC 整理 -> 仍不足则 OOM。
常见追问与易错点: Full GC 频繁不应只调大堆,还要排查泄漏、晋升过快和分配速率。
12. 只有堆会发生GC吗?
面试先答: 不是;元空间、字符串池、代码缓存等区域也可能因类卸载、常量回收或编译缓存管理而释放,直接内存由引用清理而非普通堆 GC。
详细解释: GC 主要追踪堆对象,但 GC 周期可触发类卸载和引用处理;代码缓存满会发生 sweeper,不等于对象 GC。Native 内存需独立监控。
示例或流程: 自定义类加载器卸载 -> 元空间释放;DirectBuffer 引用清理 -> native 内存释放。
常见追问与易错点: 方法区“永久不回收”是旧误解;类卸载条件严格且通常不频繁。
13. 常见的垃圾收集器有哪些?
面试先答: Serial/Serial Old、Parallel Scavenge/Old、CMS(JDK 14 移除)、G1(JDK 9 默认)、ZGC、Shenandoah;选择取决于吞吐、延迟和堆规模。
详细解释: Serial 适合小堆单线程;Parallel 偏吞吐;CMS 并发低停顿但碎片和维护复杂;G1 分区可预测;ZGC/Shenandoah 追求超低停顿。JDK 21 中 G1 仍常用,ZGC 已支持分代(JDK 21 正式)。
示例或流程:
-XX:+UseG1GC、-XX:+UseZGC(具体可用性依 JDK 版本)。常见追问与易错点: CMS 已移除不能作为新项目默认;收集器参数需配合 JDK 版本。
14. CMS和G1的区别是什么?
面试先答: CMS 以老年代并发标记清除为主、易碎片且需配合年轻代;G1 将堆划 Region,按收益选择回收集合并支持可预测暂停。CMS 在 JDK 14 已移除。
详细解释: CMS 有初始标记、并发标记、重新标记、并发清除,可能并发失败退化 Full;G1 有初始标记、并发标记、重新标记、混合回收,并用 RSet 跟踪跨区引用。
示例或流程: CMS 停顿受老年代碎片影响;G1 通过
MaxGCPauseMillis目标选择 Region(软目标而非保证)。常见追问与易错点: G1 不是无停顿;RSet 和写屏障会增加内存/CPU 开销。
15. G1垃圾回收器的回收流程是怎样的?如何实现可预测的停顿时间?
面试先答: G1 把堆切成 Region,经历 Young GC、并发标记、Remark/Cleanup,再进行包含老年代 Region 的 Mixed GC;根据每个 Region 的垃圾收益估算停顿预算。
详细解释: Region 可动态承担 Eden/Survivor/Old/Humongous;Remembered Set 提供跨 Region 引用;Evacuation 将存活对象复制到新 Region。预测依赖历史停顿模型,是软目标。
示例或流程: 选收益最高的若干 Region -> 在
MaxGCPauseMillis预算内复制 -> 更新引用。常见追问与易错点: 大对象占 humongous Region 可能造成回收压力;设置过低暂停目标会牺牲吞吐。
16. G1中Remembered Set的作用是什么?
面试先答: RSet 记录其他 Region 指向本 Region 的跨区引用,使回收某 Region 时只扫描相关卡片而不用扫描整个堆。
详细解释: 写屏障把修改记录到卡表,再维护每个 Region 的 RSet;RSet 越精确越占内存,过于膨胀会增加 GC 开销。它是并发/分区收集正确性的基础。
示例或流程: Old Region 引用 Eden 对象 -> 写屏障记录 -> Young GC 扫描 RSet 找到根。
常见追问与易错点: RSet 不是对象引用的完整副本;实现会合并卡片并异步处理。
17. ZGC颜色指针的核心解决了什么问题?
面试先答: 颜色指针把标记/重定位元数据编码到引用高位,配合读屏障实现并发移动对象和指针修复,核心目标是超低停顿。
详细解释: ZGC 在 64 位地址中保留元数据位(具体布局依版本),加载引用时由屏障染色/重映射;应用线程与 GC 并发执行。JDK 15 后 ZGC 面向生产,JDK 21 分代 ZGC 正式可用。
示例或流程: 读取旧地址 -> 读屏障查重映射表 -> 返回新地址。
常见追问与易错点: 颜色指针依赖平台和压缩指针配置;低停顿不等于零开销,吞吐和内存占用需评估。
18. 什么是读写屏障?有什么作用?
面试先答: 读屏障在读取引用时执行额外逻辑,写屏障在写引用时记录变化;用于并发标记、跨代记忆集、对象转移和指针修复。
详细解释: G1 主要依赖写屏障维护卡表/RSet;ZGC/Shenandoah 还大量使用读屏障处理并发重定位。屏障增加 CPU 成本,但换取更短停顿。
示例或流程: 写字段 -> 写屏障记录卡片;读对象引用 -> 读屏障重映射地址。
常见追问与易错点: 屏障不是 Java synchronized;不同收集器的屏障位置和语义不同。
19. 为什么JDK1.8弃用了永久代?
面试先答: 永久代位于堆内、大小固定且易受类元数据增长影响;JDK 8 改用本地内存元空间,容量更灵活并减少因永久代不足导致的 OOM。
详细解释: 元空间仍受本地内存和 MaxMetaspaceSize 限制,类卸载可归还空间;字符串池已在 JDK 7 移到堆,与永久代替换不是同一变更。
示例或流程: 大量动态代理类 -> 元空间增长 -> 触发类卸载或 Metaspace OOM。
常见追问与易错点: “元空间不会 OOM”错误;容器内存限制下 native 分配同样危险。
20. JDK15后为什么默认关闭偏向锁?
面试先答: 偏向锁维护复杂、撤销/重偏向成本在现代应用中收益有限,导致启动延迟和实现负担;JDK 15 默认关闭,后续版本逐步移除相关机制。
详细解释: 低竞争锁可通过轻量级 CAS 和自适应自旋获得较好性能;具体 HotSpot 版本可能保留实验开关但不应依赖。锁性能应以基准和实际 JDK 验证。
示例或流程:
-XX:+UseBiasedLocking在新 JDK 中可能无效或被移除。常见追问与易错点: 偏向锁关闭不等于 synchronized 变慢;不要背诵旧版“必经偏向锁升级链”。
4.3 类加载机制
1. 类加载的完整过程是什么?
面试先答: 加载、验证、准备、解析、初始化;其中验证/准备/解析属于连接,解析可延迟,初始化执行静态变量显式赋值和静态代码块。
详细解释: 加载生成 Class 对象并选择类加载器;验证检查格式和字节码安全;准备分配静态字段零值;初始化按线程安全规则执行
<clinit>。使用类字面量不一定触发初始化。示例或流程:
加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载。常见追问与易错点: 父类初始化先于子类;被动使用常量可能只读取编译期常量而不初始化类。
2. 什么是双亲委派模型?为什么要用双亲委派?
面试先答: 类加载器收到请求先交父加载器,父加载器无法找到才自己加载;用于避免核心类重复、保证类型安全和统一命名空间。
详细解释: Bootstrap、Platform、Application 构成常见层次;委派是 ClassLoader.loadClass 的约定而非 JVM 强制。相同类名由不同加载器加载仍是不同类型。
示例或流程: 加载
java.lang.String时最终由 Bootstrap 完成,应用无法替换核心类。常见追问与易错点: “父优先”不等于所有资源也父优先;线程上下文类加载器可打破方向。
3. 如何打破双亲委派机制?
面试先答: 自定义 ClassLoader 重写 loadClass、使用线程上下文类加载器、SPI、OSGi/模块化类加载等;通常为插件隔离或框架加载实现类。
详细解释: JDBC 由 Bootstrap 加载 DriverManager,但通过线程上下文加载第三方驱动;Tomcat 为每个 WebApp 使用独立加载器。打破时需明确隔离、版本冲突和安全边界。
示例或流程: 子加载器优先加载 Web 应用类,父加载器提供公共 API。
常见追问与易错点: 自定义加载器仍应优先委派核心包,避免伪造
java.*;同名类跨加载器不能直接强转。
4. 常见的类加载器有哪些?
面试先答: Bootstrap(本地代码加载核心类)、Platform/Extension(平台模块)、Application/System(classpath/module path);还包括自定义和容器加载器。
详细解释: JDK 9 将 ExtensionLoader 改为 PlatformClassLoader;
ClassLoader.getPlatformClassLoader()可获取平台加载器。加载器层次和模块边界随 JDK 版本变化。示例或流程:
String.class.getClassLoader()==null表示 Bootstrap 加载。常见追问与易错点: null 不代表没有加载器;不要把 ApplicationClassLoader 说成只能加载当前项目源码。
4.4 JVM调优与问题排查
1. JVM常用的调优参数有哪些?
面试先答: 堆
-Xms/-Xmx,新生代/晋升参数,收集器选择,GC 日志,元空间和直接内存上限,线程栈-Xss,以及 OOM dump 参数。详细解释: JDK 9+ 统一日志用
-Xlog:gc*,JDK 8 常用-XX:+PrintGCDetails;JFR、NMT、HeapDumpOnOutOfMemoryError 用于观测。参数必须和 JDK、收集器匹配。示例或流程:
-Xms2g -Xmx2g -XX:+UseG1GC -Xlog:gc*:file=gc.log(示例需按环境调整)。常见追问与易错点: 不要盲目固定新生代比例或关闭显式 GC;容器感知参数在新 JDK 默认开启但仍需核对。
2. 如何进行JVM调优?有哪些思路?
面试先答: 先定义 SLA 和现象,采集 GC/CPU/内存/线程指标,建立基线,定位分配率、存活集、停顿和线程问题,再小步调参压测验证。
详细解释: 调优顺序通常是代码和对象生命周期 -> GC/堆参数 -> 线程池和外部依赖;保留可回滚配置。使用 jstat、jcmd、JFR、async-profiler、heap dump 形成证据链。
示例或流程: 现象 -> 数据 -> 假设 -> 单变量修改 -> 压测/线上灰度 -> 复盘。
常见追问与易错点: “把堆调大”可能延长 Full GC;没有基线和压测无法证明优化有效。
3. 线上服务CPU100%怎么排查?
面试先答: 先确认进程和容器 CPU,定位高 CPU 线程,再把线程 ID 转十六进制与 jstack 对照,结合 JFR/火焰图查热点循环、锁自旋、GC 或业务代码。
详细解释: Linux 可
top -H -p pid、pidstat -t;jcmd Thread.print 获取线程栈;若是 GC 看 GC 日志和分配率,若是死循环/正则/序列化则修复代码或限流。示例或流程: top 找 TID ->
printf '%x'转 nid -> jstack 查 RUNNABLE 栈 -> 火焰图验证。常见追问与易错点: CPU 高不一定是 Java 线程,可能是 native/GC;采样多次避免单次误判。
4. 线上服务频繁GC怎么排查?
面试先答: 观察 GC 次数、停顿、分配速率和堆占用,区分 Young 频繁还是 Full 频繁,再排查对象分配过快、堆过小、晋升过快、内存泄漏和显式 GC。
详细解释: 用 GC 日志/JFR/jstat 看 Eden、Survivor、Old 变化;heap dump 比较存活对象;检查缓存、集合、临时字符串、序列化和线程本地变量。调大堆前先降低分配和修泄漏。
示例或流程: Young 高频且停顿短可能正常;Full 后 Old 仍高则重点查泄漏/存活集。
常见追问与易错点: 只看 GC 次数不看停顿和吞吐会误判;不同收集器日志字段不能直接横比。
5. 如何排查OOM问题?
面试先答: 先按错误类型区分 heap、metaspace、direct、native thread 或 stack,再保留 dump/日志,定位最大持有者和生命周期,修复后用压测验证。
详细解释: 堆 OOM 用
-XX:+HeapDumpOnOutOfMemoryError和 MAT;元空间查动态类/类加载器泄漏;直接内存用 NMT/Netty leak detector;线程 OOM 查线程数、-Xss、系统 ulimit。容器还要看 cgroup limit。示例或流程: OOM 发生 -> 采集 heap/thread/native 数据 -> 支配树/引用链 -> 修复缓存/关闭资源/限制并发。
常见追问与易错点: dump 可能很大且影响 IO,要预留磁盘;重启只能恢复服务,不能替代根因分析。
五、MySQL 数据库
5.1 索引原理
1. MySQL 索引的作用是什么?有哪些类型?
面试先答: 索引是按列建立的有序数据结构,用空间换查询时间,减少扫描行数;常见有主键、唯一、普通、联合、全文、空间索引,InnoDB 还区分聚簇与二级索引。
详细解释: 主键索引唯一且叶子存整行;二级索引叶子存索引列和主键值,回表再取整行。联合索引按列顺序排序,设计时兼顾选择性、写放大和覆盖查询。
常见追问与易错点: 索引不是越多越好,会增加 INSERT/UPDATE 成本;
LIKE '%x'通常无法使用普通 B+ 树索引。
2. MySQL 索引底层是什么数据结构?为什么用 B+ 树?
面试先答: InnoDB 默认使用 B+ 树。它多路低高度、节点与磁盘页匹配,叶子有序链表,适合等值和范围查询。
详细解释: 内节点只存键和子指针,叶子存记录指针(聚簇索引直接存行),一次 I/O 能装更多键;范围查询定位起点后顺着叶链扫描。哈希只能等值,二叉树高度高,均不适合作为通用磁盘索引。
常见追问与易错点: B+ 树“所有数据都在叶子”是 InnoDB 逻辑描述;二级索引叶子实际存主键,不是物理地址。
3. B+树和B树的区别是什么?B+树的优势是什么?
面试先答: B 树的所有节点都可存数据;B+ 树只在叶子存数据且叶子相连,因此扇出更大、树更矮,范围扫描更高效。
详细解释: B+ 树查找路径长度稳定,适合数据库页缓存与顺序 I/O;B 树找到内节点记录即可结束,但范围遍历需在不同子树间跳转。
常见追问与易错点: B+ 树并非任何场景都优于 B 树,内存索引或只做随机等值时差异要结合实现。
4. 红黑树和B+树的区别?为什么索引不用红黑树?
面试先答: 红黑树是内存二叉平衡树,B+ 树是面向磁盘/缓存的多路树;红黑树高度和随机指针跳转导致 I/O 次数多,所以数据库索引选 B+ 树。
详细解释: 百万级键的红黑树高度约 20,可能产生多次磁盘页读取;B+ 树每层可容纳数百键,通常 3~4 层即可覆盖大表。
常见追问与易错点: “不用红黑树”不是因为它不平衡,而是节点少、树高和局部性不适合磁盘。
5. 聚簇索引和非聚簇索引的核心区别是什么?
面试先答: 聚簇索引的叶子就是整行数据且一张表只有一个;非聚簇(二级)索引叶子存索引列和主键值,可有多个,命中后可能回表。
详细解释: InnoDB 主键默认聚簇;无主键时选非空唯一键,否则生成隐藏 6 字节 row ID。二级索引更新主键值会带来维护成本,主键应短且稳定。
常见追问与易错点: MyISAM 的“聚簇”概念不同,索引叶子存文件地址;不要把引擎特性混答。
6. InnoDB引擎中聚簇索引的底层存储结构及查询流程是什么?
面试先答: 聚簇 B+ 树叶子页按主键顺序存完整记录。主键等值先从根逐层定位叶页,再读取记录;二级索引查询还需拿主键回聚簇树回表。
详细解释: 页是默认 16KB 的管理单位,缓冲池缓存热点页;范围查询在叶子链上顺序读取。覆盖索引不需要回表,可减少随机 I/O。
常见追问与易错点: 回表次数不是固定一次,可能受页缓存、批量回表和 MRR 优化影响。
7. 主键索引和唯一索引的区别是什么?
面试先答: 主键每表只能一个、不可为 NULL,通常是聚簇索引;唯一索引可有多个,允许 NULL(InnoDB 中多个 NULL 通常可共存),约束业务唯一性。
详细解释: 主键是行身份和外键引用首选;唯一索引是约束加访问路径,是否聚簇取决于引擎和是否被选为聚簇键。
常见追问与易错点: 唯一索引不等于唯一业务语义,软删除场景要用联合唯一或函数/生成列设计。
8. 什么是覆盖索引?如何检查SQL是否使用了覆盖索引?
面试先答: 查询所需列全部在索引叶子中,称覆盖索引,避免回表。用
EXPLAIN看Extra: Using index。详细解释: 例如联合索引
(user_id,status,created_at)覆盖SELECT user_id,status...;不要为了覆盖把大字段放进索引,须权衡页膨胀。常见追问与易错点:
Using index condition只是索引下推,不等于完全覆盖;SELECT *很难覆盖。
9. 什么是索引下推(ICP)?核心作用是什么?
面试先答: ICP 在存储引擎扫描二级索引时先用索引中可判断的条件过滤,再回表,减少回表次数;由优化器自动决定。
详细解释: 联合索引最左列用于定位,后续列即使未完整满足最左法则,也可能在叶子上提前判断。通过
EXPLAIN的Using index condition观察。常见追问与易错点: ICP 不能突破索引排序规则,也不能把不在索引里的列提前过滤。
10. 联合索引的最左前缀法则是什么?
面试先答: 联合索引从最左列开始连续匹配,遇到范围、函数或隐式转换后通常停止继续用于定位;优化器仍可能用后续列做 ICP。
详细解释:
(a,b,c)可支持a、a,b、a,b,c;b单独查询通常无法定位。等值列放前、范围列放后是常见经验,但要以真实选择性和查询频率验证。常见追问与易错点: “范围后全部失效”过于绝对,后续列可能参与过滤;
ORDER BY、GROUP BY也会影响可用性。
11. 索引什么时候会失效?
面试先答: 列上做函数/计算、隐式类型转换、前置通配符、低选择性、联合索引未命中左前缀、
OR条件不当、范围过大都可能导致不走索引。详细解释: 优化器按成本选择,不走索引不一定是失效;可改写为范围条件、统一参数类型、拆分
OR为UNION ALL,再用执行计划验证。常见追问与易错点: 统计信息过期、数据分布倾斜会误判;可
ANALYZE TABLE,但不要随意FORCE INDEX。
12. 为什么不用哈希索引?
面试先答: 哈希索引等值查询快,但不支持排序、范围、前缀匹配且哈希冲突需处理;通用 OLTP 需要 B+ 树的有序能力。
详细解释: InnoDB 自适应哈希索引是内存优化结构,由引擎按热点自动建立,不是用户定义索引的替代品。Memory 引擎可显式使用 HASH。
常见追问与易错点: Redis 等内存 KV 使用哈希是场景选择,不代表关系库也适合。
13. 索引的cardinality是什么?
面试先答: Cardinality 是列不同值数量的估计值,选择性约为
cardinality/行数;越高通常越适合作为索引前缀。详细解释: InnoDB 通过采样统计,
SHOW INDEX可查看。统计不精确或数据倾斜时优化器可能选错索引,需更新统计信息和观察实际计划。常见追问与易错点: 高基数不是唯一标准,排序、覆盖、回表成本也决定索引价值。
14. 如何确定SQL是否使用了索引?
面试先答: 先
EXPLAIN/EXPLAIN ANALYZE看key、type、rows、filtered、Extra,再用慢日志和实际耗时验证。详细解释:
EXPLAIN ANALYZE(MySQL 8.0.18+)会执行语句并给实际行数;线上谨慎使用,先在只读副本或低峰验证。常见追问与易错点:
possible_keys只是候选,不代表使用;type=ALL可能是小表最优,不能只看一个字段下结论。
15. 如何设计索引?
面试先答: 从高频查询和数据分布出发,按过滤/连接/排序组合设计最少必要索引,优先覆盖热点查询,控制索引宽度和写放大。
详细解释: 明确联合索引列顺序、前缀长度、冷热数据;上线前用真实数据
EXPLAIN ANALYZE,上线后监控索引使用率和写延迟,定期清理冗余索引。常见追问与易错点: 不要按“每个字段一个索引”机械添加;主键自增可减少页分裂,但分布式 ID 需评估局部性。
5.2 事务与隔离级别
1. 事务的ACID四大特性是什么?
面试先答: 原子性(全成全败)、一致性(满足约束)、隔离性(并发互不干扰)、持久性(提交后不丢)。ACID 是数据库机制共同作用的结果。
详细解释: InnoDB 通过 undo/锁/MVCC/redo 与 WAL 等实现;业务一致性还需要应用校验、约束和幂等,不能只靠数据库。
常见追问与易错点: 隔离性越强并发代价越大;持久性依赖
innodb_flush_log_at_trx_commit和存储设备可靠性。
2. 事务的隔离级别有哪些?分别解决了什么问题?
面试先答: RU、RC、RR、Serializable 由低到高;分别允许/避免脏读、不可重复读、幻读。MySQL InnoDB 默认 RR。
详细解释: RU 读未提交;RC 每次语句新建快照;RR 同一事务一致性读使用首个快照,并配合 next-key lock;Serializable 通过更强锁串行化。
常见追问与易错点: 标准定义的 RR 与 InnoDB 行为有差别;当前读(
FOR UPDATE)和快照读要区分。
3. 什么是脏读、不可重复读、幻读?
面试先答: 脏读读到未提交数据;不可重复读同一行两次值不同;幻读同一条件范围的行数出现新增/删除。
详细解释: RC 主要避免脏读;RR 通过 MVCC 解决快照读幻读,当前读则依赖间隙/临键锁。业务语境中“幻读”常泛指结果集变化。
常见追问与易错点: 更新同一行导致的是不可重复读;新增满足条件的行才是典型幻读。
4. 幻读是什么?如何解决?
面试先答: 同一事务按范围读取两次,第二次多出/少了记录叫幻读;InnoDB RR 对快照读用 MVCC,对当前读用 next-key lock。
详细解释:
SELECT ... FOR UPDATE会锁记录及间隙,阻止并发插入;唯一索引等值命中时锁范围可能退化为记录锁。高并发时应缩小事务和索引范围。常见追问与易错点: MVCC 不能阻止别人提交新行,只保证快照视图一致;跨事务业务仍需显式锁或约束。
5. 读已提交和可重复读的核心区别是什么?
面试先答: RC 每条一致性读获取新快照,可能不可重复读;RR 同一事务复用快照,结果稳定但锁/历史版本保留更久。
详细解释: RC 通常减少锁等待、适合高并发读写;RR 是 MySQL 默认并兼顾一致性。选择应结合长事务、报表和写冲突测试。
常见追问与易错点: 当前读在两种级别下都读最新已提交并加锁,不要用快照读规则解释它。
6. 什么是MVCC?底层实现原理是什么?
面试先答: MVCC 用多版本记录和 Read View 实现非阻塞一致性读;InnoDB 通过隐藏事务 ID、回滚指针和 undo 版本链找到可见版本。
详细解释: Read View 按隔离级别判断版本是否已提交;快照读沿 undo 链回溯,当前读直接读最新版本并加锁。长事务会阻止旧版本清理。
常见追问与易错点: MVCC 主要服务一致性读,不等于“完全无锁”;二级索引覆盖读也要遵循可见性。
7. InnoDB事务的原子性、持久性、隔离性分别依靠什么机制保证?
面试先答: 原子性靠 undo log,持久性靠 redo log/WAL,隔离性靠锁与 MVCC;一致性由它们加约束和业务逻辑共同保证。
详细解释: 提交时 redo 先落盘,崩溃恢复重做;回滚依据 undo;锁管理并发写,Read View 提供快照。两阶段提交协调 redo 与 binlog。
常见追问与易错点:
sync_binlog、innodb_flush_log_at_trx_commit会改变可靠性与性能取舍。
5.3 锁机制
1. MySQL中常见的锁有哪些?
面试先答: 按粒度有全局、表、行锁;按模式有共享锁、排他锁;InnoDB 还有记录锁、间隙锁、临键锁、意向锁和自增锁。
详细解释: 意向锁用于快速判断表上是否存在行锁;间隙/临键锁服务 RR 下防幻读。元数据锁(MDL)保护表结构变更。
常见追问与易错点: “读锁/写锁”与共享/排他是不同命名体系;普通
SELECT快照读通常不加行锁。
2. 表级锁和行级锁的区别是什么?
面试先答: 表锁范围大、冲突多但管理开销低;行锁并发高但维护成本高且可能死锁。InnoDB 主要行锁,MyISAM 主要表锁。
详细解释: 行锁实际锁索引记录,未命中合适索引可能扫描并锁大量记录甚至表现为表级影响。DDL 还会申请 MDL。
常见追问与易错点: 行锁不是锁物理行,必须走索引;
UPDATE无索引条件是线上事故常见来源。
3. 什么情况下会升级为表锁?
面试先答: InnoDB 通常不会把大量行锁自动升级为表锁;无索引更新、显式
LOCK TABLES、DDL/MDL、MyISAM 写操作会造成表级阻塞。详细解释: 事务持有大量行锁会耗尽锁内存或造成严重竞争,但不是传统意义自动升级。排查要看
performance_schema.data_locks、MDL 和执行计划。常见追问与易错点: 把“锁升级”当成 InnoDB 默认行为是误区。
4. 乐观锁和悲观锁的区别是什么?
面试先答: 悲观锁先加锁再操作,适合冲突高;乐观锁不阻塞读取,提交时用版本号/时间戳或条件更新检测冲突,适合冲突低。
详细解释: 乐观锁示例
UPDATE ... SET version=version+1 WHERE id=? AND version=?,影响行数为 0 时重试或失败;悲观锁常用SELECT ... FOR UPDATE。常见追问与易错点: 乐观锁不是绝对无锁;版本字段必须在同一事务中校验并更新。
5. 什么是间隙锁?有什么作用?
面试先答: 间隙锁锁定索引记录之间的区间,不锁已有记录,用于 RR 当前读阻止幻读插入。
详细解释: 临键锁=记录锁+间隙锁,范围条件常形成多个临键区间;唯一索引等值命中可只加记录锁。RC 默认关闭大部分间隙锁。
常见追问与易错点: 间隙锁可能锁住“并不存在”的范围并导致插入等待,需保证条件有索引、范围尽量小。
6. MySQL死锁如何排查和解决?
面试先答: 先保留错误日志,执行
SHOW ENGINE INNODB STATUS和查询performance_schema锁等待,定位事务顺序和索引范围;通过统一加锁顺序、缩短事务、完善索引降低死锁。详细解释: InnoDB 检测到环路会回滚代价较小事务,应用必须捕获死锁错误并有限重试。线上要记录 SQL、参数、事务耗时。
常见追问与易错点: 死锁不是“数据库坏了”,可接受但要控制频率;盲目重试会放大流量。
7. InnoDB引擎的优点是什么?
面试先答: 支持事务、行级锁、MVCC、崩溃恢复、外键和聚簇索引,适合高并发 OLTP;缺点是结构复杂、写放大和锁管理成本较高。
详细解释: MySQL 8 默认 InnoDB;MyISAM 仍可用于只读/全文等特殊场景但不支持事务。生产应结合负载测试和备份恢复演练。
常见追问与易错点: 外键是否启用是工程取舍,不应简单说“必须/绝对不能”。
5.4 SQL优化
1. MySQL中执行一条SQL的完整流程是什么?
面试先答: 连接器认证与连接复用→解析器词法/语法分析→预处理→优化器选索引和执行计划→执行器调用存储引擎→返回结果并记录日志。
详细解释: InnoDB 读取缓冲池页,写操作生成 undo/redo,提交阶段与 binlog 协调;MySQL 8 默认已移除查询缓存。
常见追问与易错点: 优化器可能根据统计信息选全表扫描;“解析后直接执行”忽略了优化阶段。
2. 慢SQL如何定位?
面试先答: 开启并采集 slow query log,设置合理
long_query_time,结合 Performance Schema、监控 APM 找到 SQL 模板和调用链。详细解释: 先按总耗时、调用次数、平均/尾延迟排序,再在只读环境复现执行计划;注意区分锁等待、CPU、I/O 和网络耗时。
常见追问与易错点: 不要只看单次慢查询;线上开启日志要评估磁盘和脱敏。
3. 如何分析一条慢SQL?
面试先答: 参数化复现,
EXPLAIN ANALYZE看实际行数和耗时,核对索引、连接顺序、排序临时表,再检查锁、统计信息和数据倾斜。详细解释: 优先减少扫描行、回表和中间结果;通过改写 SQL/索引、分批处理后比较基准。优化后要验证写入成本和其他查询回归。
常见追问与易错点: 只加索引可能导致优化器选错或写性能下降,需保留前后计划。
4. EXPLAIN执行计划主要看哪些字段?
面试先答:
type、possible_keys、key、key_len、rows、filtered、Extra和 8.0 的成本/实际耗时。详细解释:
const/eq_ref/ref/range通常优于ALL;Using temporary/filesort提示额外代价,Using index表示覆盖。以实际行数和业务规模综合判断。常见追问与易错点: type 不是绝对好坏;
rows是估算值,需EXPLAIN ANALYZE校准。


5. MySQL优化器是如何选择索引的?
面试先答: 根据统计信息估算各计划的扫描行、回表、排序和 I/O 成本,选择总代价较低者;成本模型不是固定优先某个索引。
详细解释: 直方图(MySQL 8)可改善非均匀分布估算;统计过期、参数变化和相关列会导致误判。必要时更新统计或重写 SQL。
常见追问与易错点:
FORCE INDEX是临时兜底,不应代替根因修复。
6. 深度分页如何优化?
面试先答: 避免
LIMIT offset,size扫描丢弃大量行,改用基于有序唯一键的 seek/游标分页:WHERE id>? ORDER BY id LIMIT ?。详细解释: 复杂排序可用覆盖索引先取主键再回表(延迟关联);需要跳到任意页时可维护书签或限制最大页数。
常见追问与易错点: 游标分页要求稳定排序和唯一 tie-breaker;数据实时变化时要定义一致性语义。
7. 多表联合查询变慢了,如何排查和优化?
面试先答: 看每张表过滤行数、连接顺序和连接列索引,先缩小驱动表,避免隐式类型转换和大结果集;用执行计划与真实数据验证。
详细解释: 连接列两侧类型/字符集一致,必要时预聚合或拆查询;MySQL 8 可使用 hash join(满足条件时)但不能替代索引。
常见追问与易错点: 先
JOIN再在应用过滤会放大中间集;不要只给被驱动表加索引而忽视驱动表选择性。
8. 左连接(LEFT JOIN)查询时,ON和WHERE条件的核心区别是什么?
面试先答:
ON决定右表匹配条件,保留左表未匹配行;WHERE在连接结果后过滤,写右表非空条件会把 LEFT JOIN 变成 INNER JOIN。详细解释: 需要保留无明细的用户时,把右表条件放
ON:LEFT JOIN order o ON ... AND o.status=1。用WHERE o.id IS NULL可查反连接。常见追问与易错点: 优化器可能重排等价条件,但语义仍按 SQL 逻辑阶段理解。
9. 如何避免JOIN多表后出现笛卡尔积的问题?
面试先答: 每次 JOIN 都写完整且唯一/正确的关联条件,先确认表关系基数,避免漏写 ON 或多对多重复拼接。
详细解释: 多对多应先按业务键聚合、去重或使用
EXISTS;检查结果行数与预期基数,给连接列建立索引。常见追问与易错点:
DISTINCT只能掩盖错误并增加排序成本,不能作为首选修复。
10. 给几十万条数据的表新增字段时,如何避免锁表影响业务?
面试先答: 优先使用 MySQL 8 的
ALGORITHM=INSTANT(仅支持部分变更),否则采用在线 DDL 或 gh-ost/pt-online-schema-change,先评估 MDL 和回滚方案。详细解释: 低峰执行、设置锁等待超时、监控复制延迟;大表字段先允许 NULL/默认值,再分批回填,最后切换约束。
常见追问与易错点: 即使 INSTANT 也会申请短暂 MDL,长事务可能阻塞 DDL;不能承诺“完全无锁”。
5.5 日志机制
1. MySQL中常见的日志有哪些?分别的作用是什么?
面试先答: redo 保证崩溃恢复,undo 支持回滚和 MVCC,binlog 记录逻辑变更用于复制/恢复,slow log 定位慢 SQL,error log 记录服务错误。
详细解释: general log 记录所有请求但开销大,通常只临时排障;relay log 是从库接收的 binlog。日志保留、脱敏和磁盘空间需纳入运维。
常见追问与易错点: redo 是引擎层物理日志,binlog 是 Server 层逻辑日志,不能互相替代。
2. redo log和undo log的区别是什么?
面试先答: redo 记录“如何重做”保证持久性,循环写入;undo 记录“如何撤销”保证原子性并提供旧版本,事务结束后异步清理。
详细解释: redo 采用 WAL,提交前确保日志落盘;undo 版本链供 MVCC 可见性判断。两者都可能造成磁盘和空间压力。
常见追问与易错点: undo 不是备份,不能替代 binlog 的时间点恢复。
3. binlog的作用是什么?有哪些格式?
面试先答: binlog 记录提交的逻辑变更,用于主从复制和 PITR;格式有 STATEMENT、ROW、MIXED,生产通常选 ROW。
详细解释: ROW 记录行变化,避免非确定性函数导致主从不一致但日志更大;GTID 便于复制位点管理。恢复需结合全量备份和 binlog。
常见追问与易错点: binlog 默认不是实时可靠落盘,
sync_binlog影响崩溃丢失窗口。
4. slow query log是什么?有什么作用?
面试先答: 慢查询日志记录超过阈值或未使用索引的 SQL,帮助定位性能瓶颈;可用
mysqldumpslow、pt-query-digest 聚合分析。详细解释: 配置
long_query_time、log_queries_not_using_indexes要结合流量,避免日志爆量;日志包含锁等待时间的维度需配合 Performance Schema。常见追问与易错点: 慢日志是事后观测,不会自动优化 SQL。
5.6 集群与架构
1. MySQL主从复制的原理是什么?怎么确保主从一致性?
面试先答: 主库写 binlog,从库 I/O 线程拉取写 relay log,SQL/应用线程重放;用 ROW+GTID、半同步、监控延迟和校验工具提升一致性。
详细解释: 半同步只保证至少一个从库收到/落盘到一定阶段,不等于强一致读;读写分离要按业务容忍延迟路由。
常见追问与易错点: 从库延迟、网络分区、非确定性语句都会影响一致性;必须有重建和故障切换流程。
2. 读写分离怎么实现?
面试先答: 应用或代理(如 ProxySQL/MySQL Router)按读写路由,写主库、读从库,并处理故障转移和读己之写。
详细解释: 事务内读通常固定主库;写后短时间强制主库或等待 GTID 已应用,避免复制延迟导致旧读。连接池和事务边界要统一。
常见追问与易错点: 只配置读从库会引入一致性问题;从库不能承担超出复制能力的流量。
3. 分库分表了解吗?常见的分片策略有哪些?
面试先答: 数据量或并发超过单库能力时按租户/用户水平分片,或按业务垂直拆库;策略有范围、哈希、一致性哈希和时间分片。
详细解释: 分片键要高基数、稳定且能覆盖主要查询;同时解决跨分片事务、全局 ID、跨库 JOIN、扩容迁移和热点倾斜。
常见追问与易错点: 分片不是首选,先做索引、归档、读写扩展;“按时间分表”要处理跨月查询。
4. ShardingSphere的原理是什么?
面试先答: ShardingSphere 在 JDBC/代理层解析 SQL、按分片规则改写并路由到多个数据源,再归并结果,提供分片、读写分离和分布式事务能力。
详细解释: 标准分片、广播表、绑定表可减少笛卡尔路由;分页、排序、聚合会在归并阶段产生额外开销,需限制跨分片查询。
常见追问与易错点: 它不是数据库集群本身,元数据、扩容迁移和故障仍需运维方案。
5. 数据冷热分离怎么做?
面试先答: 按访问频率/时间把热数据留在线主库,冷数据归档到低成本库、对象存储或数仓;通过分层访问和归档任务保持可追溯。
详细解释: 设计冷热判定、迁移幂等、校验、回迁和合规保留;历史查询可走专用库,避免拖慢在线事务。
常见追问与易错点: 冷数据不是删除,必须有恢复演练和一致性校验。
6. 关系型数据库和非关系型数据库的区别是什么?
面试先答: 关系库有固定模式、SQL、事务和关联约束;NoSQL 多为灵活模型、水平扩展和高吞吐,常弱化跨记录事务。
详细解释: 选型看访问模式和一致性要求,而非“谁更快”;实际系统常用 MySQL 做事实源,Redis/ES/Mongo 做缓存或特定查询。
常见追问与易错点: NoSQL 并非没有事务,具体能力以产品版本为准。
5.7 分布式事务
1. 分布式场景下,如何保证MySQL事务的分布式一致性?
面试先答: 优先拆成可重试的最终一致流程;强一致才考虑 Seata AT/TCC、事务消息或 Saga,并配合幂等、补偿、对账和监控。
详细解释: 先明确一致性边界、超时和故障语义,再选择方案;跨库强事务会牺牲可用性与吞吐,不宜默认上 2PC。
常见追问与易错点: 分布式事务框架不能消除业务幂等和悬挂问题。
2. CAP定理是什么?BASE理论是什么?
面试先答: 分区发生时一致性(C)和可用性(A)只能择一;BASE 提倡基本可用、软状态、最终一致,工程上按场景取舍。
详细解释: CAP 的 P 通常无法避免,CP 系统优先一致性,AP 系统允许暂时不一致;BASE 需要补偿、对账和收敛机制。
常见追问与易错点: CAP 不是“三选二”的静态标签,而是分区期间的取舍。
3. 2PC分布式协议的原理是什么?最大的缺陷是什么?
面试先答: 准备阶段参与者投票,提交阶段协调者广播 commit/rollback;缺陷是同步阻塞、协调者单点、参与者不确定状态和网络分区下可用性差。
详细解释: 日志可帮助恢复,但不能解决协调者长期不可达;适合短事务、强一致且参与者可靠的内部场景。
常见追问与易错点: 2PC 的“原子性”依赖所有参与者,不能承诺绝对不丢。
4. 3PC相比2PC有什么改进?
面试先答: 增加 CanCommit 预检查和 PreCommit 阶段,引入超时让参与者在协调者失联时尝试提交,降低阻塞;但仍无法彻底解决网络分区和数据不确定。
详细解释: 三阶段增加消息和状态复杂度,实际生产较少直接使用,通常选事务消息、TCC 或框架化方案。
常见追问与易错点: 超时自动提交可能与协调者决策冲突,不能把 3PC 当成强可用 2PC。
5. TCC模式的原理是什么?如何解决空回滚、悬挂问题?
面试先答: 业务提供 Try/Confirm/Cancel 三个幂等接口;Try 预留资源,Confirm 正式提交,Cancel 释放。用事务 ID 状态表、幂等检查和防悬挂时间戳处理异常。
详细解释: 空回滚是 Try 未成功却收到 Cancel;悬挂是 Cancel 先到后 Try。记录分支状态并拒绝非法状态迁移,所有接口可重试。
常见追问与易错点: TCC 开发成本高,需业务可拆分且资源可预留;Cancel 不能依赖 Try 一定执行。
6. Seata AT模式是什么?
面试先答: AT 通过代理数据源自动记录业务 SQL 前后镜像和全局锁,提交本地事务后由协调器异步二阶段确认或回滚,侵入小于 TCC。
详细解释: 一阶段提交并写 undo_log,二阶段删除或按镜像补偿;要求 SQL 可解析、隔离级别和锁冲突可控。高并发热点仍可能阻塞。
常见追问与易错点: AT 是最终一致补偿,不等于数据库原生强一致;DDL、复杂 SQL、跨资源场景有边界。
5.8 其他基础
1. UNSIGNED属性的作用是什么?
面试先答: 无符号数去掉负数范围,把同样字节用于更大正数,例如
INT UNSIGNED为 0~约 42.9 亿。详细解释: 连接/比较两列时符号必须一致,否则发生转换或报错;跨语言映射要注意 Java 没有无符号基本整型的直接对应。
常见追问与易错点:
AUTO_INCREMENT不等于必须 UNSIGNED,需评估迁移兼容。
2. char和varchar的区别是什么?
面试先答: CHAR 定长,读取快但可能填充空格;VARCHAR 变长,节省空间但带长度字节。长度按字符数定义,实际存储受字符集影响。
详细解释: 固定长度状态码可用 CHAR,用户输入和可变文本用 VARCHAR;索引长度要按字节和排序规则估算。
常见追问与易错点: VARCHAR(255) 不是固定占 255 字节;尾部空格比较规则需看 SQL 模式和字符集。
3. 为什么不推荐使用text和blob类型?
面试先答: 大字段会增加行外存储、I/O、排序临时表和备份成本,难以建立完整索引;应把大内容放对象存储,库中保存 URL/摘要。
详细解释: 必须入库时限制大小、按需读取并设置独立表;MySQL 8 支持前缀索引但不能覆盖全部内容。
常见追问与易错点: “绝对不能用”不准确,文档、富文本等场景可用但要做容量与访问隔离。
4. timestamp和datetime的区别是什么?
面试先答: TIMESTAMP 通常 4 字节、范围较小并按会话时区转换;DATETIME 8 字节、不做时区转换、范围更大。审计时间常统一 UTC。
详细解释: MySQL 5.6+ 两者都可带小数秒;跨时区系统建议存 UTC DATETIME 或明确 TIMESTAMP 约定。
常见追问与易错点: 不要把 TIMESTAMP 说成“自动更新时间”本身,默认值/
ON UPDATE需显式配置。
5. Null和''的区别是什么?
面试先答: NULL 表示未知/不存在,空字符串是长度为 0 的确定值;NULL 参与比较要用
IS NULL,不能= NULL。详细解释: NULL 引入三值逻辑,聚合通常忽略 NULL;索引和唯一约束行为需结合数据库规则。接口层应统一空值语义。
常见追问与易错点:
COUNT(*)与COUNT(col)对 NULL 统计不同。
6. MySQL中布尔值怎么表示?
面试先答:
BOOLEAN/BOOL是TINYINT(1)别名,0 表示假、非 0 表示真;应用层用布尔类型映射并校验取值。详细解释: MySQL 不提供独立布尔存储类型;需要三态时用 NULL+0/1 或枚举并明确语义。
常见追问与易错点: 不要依赖任意非 0 值,约束可用
CHECK(MySQL 8.0.16+ 实际执行)。
7. MySQL可以存图片吗?为什么不推荐?
面试先答: 可以用 BLOB 存,但大对象会占数据库空间、拖慢备份和复制;生产常放 OSS/对象存储,MySQL 只保存地址、哈希和元数据。
详细解释: 需要事务绑定时可先写对象存储临时键,再在数据库提交后确认;删除要有异步清理和防盗链。
常见追问与易错点: 金融凭证等小文件有合规要求时可入库,但要分表、限流和备份演练。
8. 内连接和外连接的区别是什么?
面试先答: INNER JOIN 只返回两侧都匹配的行;LEFT/RIGHT/FULL OUTER JOIN 会保留一侧或两侧未匹配行并用 NULL 补齐(MySQL 原生无 FULL JOIN,需 UNION 模拟)。
详细解释: 连接条件写在 ON,结果过滤写 WHERE;外连接可用来查缺失数据。
常见追问与易错点: MySQL
RIGHT JOIN可改写为交换表的 LEFT JOIN,FULL JOIN 不能直接写关键字。
六、Redis
6.1 基础特性与数据结构
1. 什么是Redis?有什么特点?
面试先答: Redis 7 是基于内存的开源数据结构服务器,支持持久化、复制、集群和丰富原子命令,常作缓存、计数器、队列和分布式协调组件。
详细解释: 数据在内存中执行,网络协议简单,延迟低;持久化和复制提供一定可靠性但不能天然替代关系库事实源。
常见追问与易错点: Redis Cluster 不支持任意跨槽事务;容量、热 key 和持久化策略要按业务评估。
2. Redis为什么速度这么快?
面试先答: 内存访问、单线程事件循环减少锁竞争、I/O 多路复用、紧凑数据结构和批量/流水线命令共同降低延迟。
详细解释: Redis 6+ 网络 I/O 可多线程,但命令执行仍主要单线程以保持简单原子语义;慢命令、阻塞式模块和大 key 仍会卡住事件循环。
常见追问与易错点: “单线程所以永远不阻塞”错误,
KEYS、大范围SMEMBERS等命令会阻塞。
3. Redis是单线程还是多线程?为什么用单线程?
面试先答: 命令执行核心是单线程;Redis 6 起可配置多线程处理网络读写,后台持久化/删除也有线程。单线程让命令原子性和数据结构实现简单。
详细解释: 多线程网络层只在读写协议上并行,命令仍按事件循环串行;要提升吞吐可分片、Pipeline 或 Cluster。
常见追问与易错点: 不要把后台 fork、IO 线程称为命令并发执行。
4. Redis的应用场景有哪些?
面试先答: 热点缓存、Session、分布式锁、计数器/限流、排行榜、集合去重、延迟队列和发布订阅。
详细解释: 场景要定义一致性、过期、容量、恢复和降级策略;Streams 比 Pub/Sub 更适合可追踪消费。
常见追问与易错点: Redis Pub/Sub 不持久化,订阅者离线会丢消息。
5. Redis有哪些数据类型?分别的底层实现是什么?
面试先答: String、Hash、List、Set、Sorted Set,及 Streams、Bitmap、HyperLogLog、Geo 等;底层按编码使用 SDS、listpack、quicklist、哈希表、跳表+哈希表。
详细解释: Redis 7 使用 listpack 替代 ziplist;编码会随大小阈值自动转换。选择类型应围绕访问命令和内存占用。
常见追问与易错点: “List 一定是双向链表”是旧版本简化说法,现代 Redis 为 quicklist。
6. String类型的底层实现是什么?SDS是什么?
面试先答: String 由 SDS(Simple Dynamic String)保存二进制安全字节序列,记录长度与容量,支持预分配和惰性空间释放。
详细解释: SDS 避免 C 字符串求长度 O(n)、自动扩容并能存任意二进制;String 适合值、计数、位图和分布式锁 token。
常见追问与易错点: 单个 String 最大约 512MB(版本和配置相关),不应存超大 JSON 频繁更新。
7. Hash类型的底层实现是什么?什么是渐进式rehash?
面试先答: Hash 小对象用 listpack,大对象用哈希表;扩容时建立新表,读写期间每次迁移少量桶,避免一次性阻塞,这叫渐进式 rehash。
详细解释: rehash 期间新写入进入新表,查找两张表;后台迁移结束后释放旧表。字段很多时应拆分或限制 value 大小。
常见追问与易错点:
HGETALL大 Hash 仍会一次返回全部字段,需HSCAN分批。
8. List/Set/ZSet的底层实现分别是什么?
面试先答: List 是 quicklist(双向链表节点内嵌 listpack);Set 小整数集合可用 intset,否则哈希表;ZSet 通常是 dict+skiplist。
详细解释: Set 提供成员去重和 O(1) 判断;ZSet 同时按 member 定位和 score 排序。Redis 7 的编码细节以
OBJECT ENCODING为准。常见追问与易错点: ZSet 的综合操作不是都 O(1),范围查询约 O(logN+M)。
9. 什么是跳跃表?原理是什么?
面试先答: 跳表在有序链表上增加多层索引,按概率提升层级,查找/插入/删除平均 O(logN),实现简单且支持范围遍历。
详细解释: ZSet 用跳表按 score 排序,配合字典按 member O(1) 找节点;最坏复杂度 O(N),但随机层级和工程参数使性能稳定。
常见追问与易错点: 跳表不是二叉树,范围遍历顺着底层链表完成。
10. 各数据类型的常见应用场景是什么?
面试先答: String 做缓存/计数,Hash 做对象字段,List 做简单队列,Set 做去重/交集,ZSet 做排行榜,Stream 做可确认消息流,Bitmap/HyperLogLog 做统计。
详细解释: 选型看操作原子性和数据规模,例如排行榜用
ZINCRBY,签到用 Bitmap;需要可靠消费时使用 Stream 的 group/ACK。常见追问与易错点: 单 key 成员过多会形成大 key,应分片或按时间拆 key。
6.2 持久化机制
1. Redis的持久化机制有哪些?分别怎么实现?
面试先答: RDB 定时生成二进制快照,AOF 追加写命令日志;可启用混合持久化,AOF 重写时前缀放 RDB、后续增量写 AOF。
详细解释: RDB 恢复快且文件小,可能丢最近数据;AOF 可按
appendfsync always/everysec/no取舍,重写压缩历史命令。常见追问与易错点: 持久化不等于高可用,仍需复制、哨兵/集群和备份。
2. RDB和AOF的区别是什么?
面试先答: RDB 是某时刻快照,恢复快、文件小但丢失窗口大;AOF 记录写命令,数据更完整但文件/恢复时间更大。
详细解释: Redis 7 推荐混合 AOF;生产根据 RPO/RTO、磁盘和恢复演练选择,重要数据可双开。
常见追问与易错点: AOF 也可能因系统崩溃丢数据,取决于 fsync 策略。
3. RDB会丢数据吗?为什么?
面试先答: 会。RDB 只保存最近一次成功快照,快照间隔内宕机的写入无法恢复;fork 失败或磁盘满也会导致快照失败。
详细解释: 通过缩短 save 周期、AOF everysec、复制和外部备份降低 RPO;fork 期间 Copy-On-Write 会增加内存压力。
常见追问与易错点:
BGSAVE成功不代表持久化到远端,备份要验证可恢复。
4. AOF重写是什么?后台重写的原理?
面试先答: AOF 重写读取当前内存状态生成最小命令集合,压缩历史日志;
BGREWRITEAOFfork 子进程执行,父进程把期间新命令写入重写缓冲区,结束后合并并原子替换。详细解释: 子进程采用 COW,修改越多额外内存越大;重写失败保留旧文件。监控
aof_rewrite_in_progress、磁盘和 fork 延迟。常见追问与易错点: 重写不是简单删除旧命令,必须生成可恢复的等价状态。
5. 什么是混合持久化?
面试先答: AOF 重写文件前半段是 RDB 快照,后半段是重写期间的增量 AOF,兼顾恢复速度和数据完整性(
aof-use-rdb-preamble yes)。详细解释: Redis 4+ 支持,Redis 7 常用;老版本客户端/运维工具要确认格式兼容。
常见追问与易错点: 混合文件仍需 fsync 和备份,不能消除磁盘故障风险。
6. Redis持久化方式如何选择?
面试先答: 纯缓存可只做 RDB/关闭持久化;可恢复数据通常 AOF everysec+RDB/混合;强 RPO 需外部多副本和备份,按 RTO/RPO 选型。
详细解释: 评估 fork、磁盘、恢复耗时和写入放大,压测故障恢复;持久化失败要告警并阻止无限写入。
常见追问与易错点: 不同业务 key 不能在同一实例随意假设相同可靠性,必要时拆实例。
6.3 过期与淘汰策略
1. Redis的key过期删除策略有哪些?
面试先答: 惰性删除(访问时检查)、定期删除(周期抽样)和主动过期扫描;实际采用惰性+定期结合,平衡 CPU 与内存。
详细解释: 过期时间存于 expires 字典;集群/主从中过期由主节点主导并传播删除。过期不保证到点立即删除。
常见追问与易错点: 过期 key 仍可能短暂占内存,不能作为精确计时器。
2. Redis采用的过期删除策略是什么?
面试先答: 惰性删除+定期主动删除;定期任务抽样过期 key,超过时间预算会暂停,避免阻塞命令执行。
详细解释: 主动过期由 hz/cron 参数影响;大批量同时过期会形成 CPU 峰值,应加随机 TTL。
常见追问与易错点: 过期删除与内存淘汰是两条机制,前者针对 TTL,后者针对内存上限。
3. Redis的内存淘汰策略有哪些?
面试先答:
noeviction、allkeys-lru/lfu/random、volatile-lru/lfu/random、volatile-ttl 等;Redis 7 LRU/LFU 是近似算法。详细解释: allkeys 适合全是缓存,volatile 只淘汰带 TTL 的 key;设置
maxmemory前要估算碎片和副本开销并压测。常见追问与易错点: 淘汰策略不会替代业务 TTL;误删热点数据时需降级和监控命中率。
6.4 缓存常见问题
1. 什么是缓存穿透?如何解决?
面试先答: 请求查询不存在的数据,缓存和数据库都未命中,恶意/高频请求打穿数据库;用缓存空值、布隆过滤器、参数校验和限流。
详细解释: 空值设置短 TTL 防止脏占用;布隆过滤器有误判无漏判,数据新增要同步更新。多层防线配合监控。
常见追问与易错点: 布隆过滤器不能判断删除后的精确状态,需重建或使用可删除结构。
2. 什么是缓存击穿?如何解决?
面试先答: 某个热点 key 过期瞬间大量请求同时回源;用互斥锁/单飞、逻辑过期异步刷新、热点不过期和预热。
详细解释: 只有一个线程重建,其他线程读旧值或短暂等待;锁要设置超时和 token,防止死锁。重建失败应有降级。
常见追问与易错点: 击穿是单热点,雪崩是大量 key/实例同时失效,概念不要混用。
3. 什么是缓存雪崩?如何解决?
面试先答: 大量 key 同时过期或 Redis 故障导致请求集中打库;通过 TTL 随机化、分批预热、多级缓存、集群容灾、限流熔断和降级。
详细解释: 设计缓存不可用时的静态/本地兜底,数据库连接池要有上限;恢复时采用令牌和预热避免二次冲击。
常见追问与易错点: 只加 Redis 从库不能解决缓存击穿,需保护回源链路。
4. 缓存预热怎么做?
面试先答: 发布前或启动后按热点清单批量加载,控制并发和过期时间,校验命中率;可通过定时任务和访问日志动态更新。
详细解释: 预热任务必须幂等、可断点续跑、错峰执行;大 key 采用 pipeline/分片,避免阻塞 Redis。
常见追问与易错点: 预热全部数据会浪费内存,优先覆盖热点和关键路径。
5. 如何保证数据库和缓存的一致性?
面试先答: 常用“先更新数据库、再删除缓存”,配合延迟双删、消息重试和订阅 binlog;强一致场景直接读库或用事务性缓存方案。
详细解释: 先删缓存再写库可能被并发读回旧值;延迟双删只能降低窗口,不能替代版本校验和最终对账。更新缓存要避免并发覆盖旧值。
常见追问与易错点: Redis 与 MySQL 不在同一事务,无法承诺瞬时强一致;明确可接受不一致时长。
6. 什么是大key问题?怎么处理?
面试先答: 单 key 的 value/集合过大导致命令阻塞、网络包大、删除耗时和内存倾斜;用
SCAN/SSCAN分批发现,拆分 key、分页存储和异步删除。详细解释:
UNLINK把释放放后台线程但仍会占内存一段时间;Hash/List 设计分桶,读写使用 pipeline。常见追问与易错点: 大 key 与热 key 可同时存在,需分别监控大小、访问频率和槽位。
7. Redis内存碎片率高的原因是什么?如何排查和优化?
面试先答: 频繁分配释放、jemalloc arena、数据大小变化和 COW 会造成碎片;查看
mem_fragmentation_ratio、allocator 指标,必要时主动碎片整理或重启迁移。详细解释: 比率低于 1 可能是 swap/内存不足,不能只看“越高越坏”;启用
activedefrag要压测 CPU。常见追问与易错点: 复制和 RDB fork 期间内存峰值可能暂时升高,应预留余量。
8. 为什么不用本地缓存?本地缓存和分布式缓存如何选择?
面试先答: 本地缓存延迟最低但多实例不一致、容量有限;分布式缓存共享、可统一淘汰但有网络和高可用成本。常用本地+Redis 两级缓存。
详细解释: Caffeine 适合热点只读数据,需版本号/发布通知失效;Redis 适合跨实例共享状态。明确一致性和故障降级优先级。
常见追问与易错点: 本地缓存不能存强一致余额等数据,更新通知丢失要有 TTL 兜底。
6.5 分布式锁实现
1. 分布式锁的原理是什么?设计分布式锁需要考虑哪些问题?
面试先答: 通过共享存储的互斥占有、租约超时和唯一 token 实现跨进程互斥;需考虑原子加锁、自动续期、释放归属、重入、故障和时钟。
详细解释: 锁只保护短临界区,业务仍要幂等;必须定义锁丢失后的安全语义,必要时用 fencing token 防止旧持有者写入。
常见追问与易错点: “设置过期时间就安全”错误,GC 停顿可能导致租约失效。
2. 如何基于Redis实现分布式锁?
面试先答:
SET lock:value token NX PX ttl原子加锁;释放用 Lua 校验 token 后DEL,避免误删他人锁。续期需校验所有权。详细解释: 获取失败按退避重试;TTL 应小于业务可接受执行窗口并有 watchdog;业务结果要幂等,Redis 故障时定义降级。
常见追问与易错点: 不能分开执行 SETNX/EX;不能直接
DEL key。
3. SETNX的功能是什么?先执行set nx再执行set ex会存在什么问题?
面试先答: SETNX 仅在 key 不存在时写入;先 SETNX 再 SETEX 不是原子操作,进程在两步之间崩溃会留下无过期锁。
详细解释: 应使用
SET key value NX EX seconds一条命令;旧 Redis 可 Lua 封装。异常恢复可设置巡检,但不能替代原子命令。常见追问与易错点: EXPIRE 对不存在 key 无效;锁值必须随机且唯一。
4. 加锁后进程异常退出,锁泄露怎么办?
面试先答: 设置 TTL 自动释放;长任务用可靠续期并在 finally 校验 token 释放,超时后业务要能重试/补偿。
详细解释: watchdog 续期线程自身故障要有上限;使用 fencing token 让下游拒绝旧锁持有者的写请求。
常见追问与易错点: TTL 过短会锁提前失效,过长会影响恢复,需要按 P99 执行时间和 GC 停顿设置。
5. 分布式锁的可重入性如何实现?
面试先答: value 记录持有者线程标识和重入次数,首次加锁设 TTL,再次同 owner 计数加一;释放时减计数,归零才删除。
详细解释: Redis Hash 可存 owner→count,Lua 保证判断、计数和续期原子;跨线程/进程重入身份必须明确。
常见追问与易错点: 只判断进程 ID 会导致同进程不同线程误重入;watchdog 要覆盖重入锁。
6. Redisson分布式锁的实现原理是什么?使用注意事项?
面试先答: Redisson 用 Lua 保证加解锁原子,以 Hash 保存线程重入计数,watchdog 默认续期;提供公平锁、读写锁等封装。
详细解释: 配置
leaseTime后 watchdog 不再无限续期;网络分区、Redis 主从切换可能造成锁安全窗口,关键资源配 fencing/幂等。常见追问与易错点: 锁对象名称必须稳定,不能在 finally 前丢失引用;不要把它当数据库事务替代品。
7. 什么是红锁(Redlock)?原理是什么?
面试先答: Redlock 在多个独立 Redis 主节点上尝试加同一锁,规定时间内多数节点成功且总耗时小于 TTL 才算获得,用于降低单实例故障风险。
详细解释: 需估算时钟漂移、网络延迟和有效剩余时间;Martin Kleppmann 等指出异步系统下仍有安全争议,强一致场景更推荐带 fencing 的协调服务(如 ZooKeeper/etcd)。
常见追问与易错点: Redlock 不是“绝对安全”或必须方案,先评估业务后果和运维复杂度。
6.6 集群与高可用
1. Redis主从复制的原理是什么?
面试先答: 从节点发送 PSYNC,主节点提供全量 RDB 或基于 repl backlog 的增量命令;复制默认异步,从库只读并持续应用。
详细解释:
runid、复制偏移和 backlog 决定部分重同步;主从一致是最终的,读从库可能有延迟。常见追问与易错点: 从库不主动选主,故障转移需 Sentinel 或外部系统。
2. Redis哨兵(Sentinel)模式的作用是什么?选举机制是什么?
面试先答: Sentinel 监控主从、发现故障、选举领导者并自动把从库提升为主,还向客户端提供配置发现。
详细解释: 主观下线由单 Sentinel 判断,客观下线需达到 quorum;Sentinel 通过 Raft-like 选举领导者,按复制偏移、优先级和 runid 选新主。
常见追问与易错点: quorum 不是集群总节点数;客户端要正确处理拓扑刷新和旧主恢复。
3. Redis Cluster的原理是什么?
面试先答: Cluster 将 16384 个 hash slot 分配给多个主节点,每个主负责槽及其副本;客户端按 CRC16 路由,遇到 MOVED/ASK 重定向。
详细解释: 节点间 gossip 检测故障并选主;多 key 命令需同槽,可用
{tag}。扩缩容通过槽迁移完成。常见追问与易错点: Cluster 不是强一致分布式数据库,故障切换存在丢写窗口;不要随意使用跨槽事务。
4. Redis哨兵和Cluster集群的核心区别是什么?怎么选?
面试先答: Sentinel 主要解决单主高可用,数据仍集中;Cluster 同时提供分片扩容和高可用。数据量/吞吐需水平扩展选 Cluster,否则 Sentinel 更简单。
详细解释: Cluster 客户端和运维复杂度更高,跨槽限制明显;Sentinel 适合中小数据且依赖代理/分片层扩容。
常见追问与易错点: Sentinel 不是分片方案,Cluster 也不是自动解决热 key。
6.7 实际业务应用
1. Redis除了做缓存还有哪些功能?
面试先答: 计数器、排行榜、集合运算、分布式锁、限流、Stream 消息、延迟任务、地理位置和实时统计。
详细解释: 选择命令前评估持久化、消费确认、顺序和容量;涉及核心账务时 Redis 只做加速或中间状态,不做唯一事实源。
常见追问与易错点: Pub/Sub 无历史与 ACK,不适合可靠消息。
2. 如何基于Redis实现接口限流?
面试先答: 用 Lua+时间窗口计数,或令牌桶 Hash/String 记录令牌和时间;按用户/IP/接口维度设置 TTL,超限返回 429。
详细解释: 固定窗口简单但边界突发,滑动窗口更平滑;要防 key 爆炸、时钟和集群跨槽,失败时采用本地兜底。
常见追问与易错点: 限流不是并发控制,慢请求还需信号量/线程池隔离。
3. 如何基于Redis实现延迟队列?
面试先答: 用 ZSet 的 score 存到期时间,消费者
ZRANGEBYSCORE取到期任务并用 Lua 原子抢占;或使用 Redis 7 Stream+定时扫描。详细解释: 任务需状态、重试次数和幂等 ID;抢占后宕机要有超时回收。高可靠场景优先使用 MQ 原生延迟消息。
常见追问与易错点:
BLPOP不能按时间延迟;轮询间隔决定精度和 CPU。
4. 如何基于Redis实现消息队列?
面试先答: 简单队列可 List
LPUSH/BRPOP;可靠消费用 Stream consumer group、XREADGROUP、ACK 和 pending 重试。详细解释: 设计消费幂等、死信、积压监控和 trim 策略;跨服务关键消息使用 Kafka/RocketMQ 等专业 MQ。
常见追问与易错点: List 出队后宕机会丢消息,需处理中队列或 Stream。
5. Redis怎么保证库存扣减的可靠性,防止多扣/多增?
面试先答: 用 Lua 在 Redis 内原子校验并扣减,订单号做幂等集合;最终以数据库唯一约束/状态机校准,消费失败可补偿。
详细解释: 库存预扣、支付超时释放和重复回调都要状态化;Redis 与 MySQL 不一致时通过消息重试、对账和人工修复收敛。
常见追问与易错点: 仅
DECR无法防重复扣减或超卖,必须绑定业务订单 ID。
6. 多机器部署本地缓存如何保证数据一致性?
面试先答: 采用短 TTL+版本号,并通过 Redis Pub/Sub、Stream 或 MQ 广播失效;更新以数据库版本为准,通知丢失由定时校验兜底。
详细解释: Caffeine 多级缓存要处理并发重建和旧值覆盖;强一致数据不放本地缓存,或每次带版本验证。
常见追问与易错点: Pub/Sub 丢消息,不能单独承担一致性;通知风暴需合并和限速。
七、计算机网络
7.1 TCP/UDP
1. TCP和UDP的区别是什么?
面试先答: TCP 面向连接、可靠、有序、流式传输并有拥塞控制;UDP 无连接、报文边界保留、开销小但不保证到达和顺序。TCP 适合 HTTP,UDP 适合实时音视频/DNS。
详细解释: TCP 通过序列号、ACK、重传和窗口控制可靠性;UDP 可靠性由应用层补充(QUIC 即在 UDP 上实现)。
常见追问与易错点: UDP 不等于一定快,丢包和拥塞时需自行处理。
2. TCP是如何保证可靠传输的?
面试先答: 序列号保证顺序,确认和超时重传保证到达,滑动窗口控制流量,校验和检测损坏,拥塞控制避免网络过载。
详细解释: 快速重传响应重复 ACK,SACK 可报告多个缺口;接收窗口与拥塞窗口共同限制在途数据。
常见追问与易错点: TCP 只保证字节流可靠,不保证业务消息边界。
3. TCP三次握手的过程是什么?为什么不是两次?
面试先答: 客户端 SYN,服务端 SYN+ACK,客户端 ACK;三次既确认双方收发能力,又同步初始序列号并避免历史 SYN 造成半连接。
详细解释: 服务端收到第三次 ACK 才可确认客户端确实收到自己的序列号;SYN Flood 利用半连接队列,可用 SYN Cookie 等防护。
常见追问与易错点: 握手完成不代表应用数据已处理;两次无法同时确认服务端接收能力和旧报文问题。
4. TCP四次挥手的过程是什么?为什么需要四次挥手?
面试先答: 主动方 FIN,收到 ACK;被动方数据发完后 FIN,主动方再 ACK。TCP 全双工,两个方向需分别关闭,所以通常四次。
详细解释: 被动方可在 ACK 后继续发送剩余数据;双方进入 FIN_WAIT、CLOSE_WAIT、TIME_WAIT 等状态。
常见追问与易错点:
CLOSE_WAIT多说明应用未关闭 socket;四次可因捎带 ACK 合并为三次。
5. TIME_WAIT状态为什么要等2MSL?
面试先答: 等待 2MSL 确保最后 ACK 丢失时可重传,并让旧连接报文在网络中失效,避免影响后续同四元组连接。
详细解释: TIME_WAIT 通常由主动关闭方持有;大量短连接会耗尽端口,可启用连接复用、长连接或合理内核参数。
常见追问与易错点: 随意开启
tcp_tw_reuse要确认系统版本和 NAT 场景,不能简单调小 2MSL。
6. TCP拥塞控制的流程是什么?
面试先答: 慢启动指数增加拥塞窗口,达到阈值进入拥塞避免线性增加;丢包触发快速重传/快速恢复或超时重传并降低窗口。
详细解释: CUBIC、BBR 等算法不同,Linux 默认版本可能变化;流量控制看接收窗口,拥塞控制看网络容量。
常见追问与易错点: “窗口越大越快”错误,过大造成排队和丢包。
7. TCP粘包和拆包问题是什么?如何解决?
面试先答: TCP 是无消息边界的字节流,一次读可能包含半包或多包;应用协议必须定义长度字段、固定长度、分隔符或自描述编码并循环读取。
详细解释: Netty 常用 LengthFieldBasedFrameDecoder;发送端
TCP_NODELAY只能影响合并,不能从根本上提供边界。常见追问与易错点: UDP 天然有报文边界但仍可能丢包、乱序和截断。
7.2 HTTP/HTTPS
1. HTTP协议的工作原理是什么?
面试先答: 客户端通过请求行/头/体发送资源请求,服务端返回状态行/头/体;HTTP 是无状态应用层协议,状态通过 Cookie、Token 等维护。
详细解释: 连接可复用,缓存、代理、内容协商和压缩降低开销;幂等方法与状态码帮助重试。
常见追问与易错点: HTTP 无状态不等于不能有会话,状态存储在客户端或服务端。
2. HTTP1.0、HTTP1.1、HTTP2.0的区别是什么?
面试先答: 1.0 默认短连接;1.1 默认持久连接、Host、分块传输;2.0 二进制分帧、多路复用、头部压缩和服务器推送(已逐步废弃)。
详细解释: HTTP/2 多路复用缓解队头阻塞但 TCP 丢包仍会阻塞所有流;HTTP/3 用 QUIC/UDP 改善传输层队头阻塞。
常见追问与易错点: HTTP/2 不等于一定更快,代理、TLS 和服务端实现影响结果。
3. HTTP和HTTPS的区别是什么?
面试先答: HTTPS 是 HTTP over TLS,提供加密、完整性和服务端身份认证,默认 443;HTTP 明文且易被窃听篡改。
详细解释: 证书链验证域名和公钥,TLS 会话密钥保护后续数据;HTTPS 不能防止服务端应用漏洞和业务逻辑攻击。
常见追问与易错点: “有 HTTPS 就绝对安全”错误,证书校验、版本和密钥管理同样关键。
4. HTTPS的握手过程是怎样的?为什么需要三个随机数?
面试先答: ClientHello/ServerHello 协商版本、套件和随机数,服务端发证书并证明私钥,双方通过 ECDHE 等生成共享密钥,Finished 校验后传输加密数据。
详细解释: 传统教材称客户端随机数、服务端随机数、预主密钥“三个随机数”;TLS 1.3 主要使用双方随机数和 ECDHE 共享秘密,前向保密来自临时密钥。
常见追问与易错点: RSA 密钥交换已不推荐;TLS 1.3 握手更少往返,不能套用 TLS 1.2 全部步骤。
5. HTTPS的性能开销在哪里?
面试先答: 首次握手增加 CPU 加密、证书验证和网络往返;连接复用、会话恢复、TLS 1.3、硬件加速可降低开销。
详细解释: 对称加密处理业务数据很快,主要成本在非对称握手;开启 HTTP/2 时 TLS 通常是前提。
常见追问与易错点: 不应因性能关闭 HTTPS,优先优化长连接和会话复用。
6. 什么是中间人攻击?HTTPS如何防御?
面试先答: 攻击者插入通信两端窃听或篡改;HTTPS 通过 CA 信任链、域名校验、数字签名和加密检测/防止伪造。
详细解释: 客户端必须验证证书有效期、SAN、链和吊销策略;证书固定可降低被错误 CA 签发风险但会增加运维复杂度。
常见追问与易错点: 忽略证书校验或信任所有证书会直接破坏 TLS 安全。
7. HTTP3和QUIC了解吗?
面试先答: HTTP/3 基于 QUIC(UDP),在用户态实现可靠传输、TLS 1.3、连接迁移和流级别多路复用,减少 TCP 队头阻塞。
详细解释: QUIC 对每个 stream 独立丢包恢复,适合移动网络;部署需网关、负载均衡和 UDP 防火墙支持。
常见追问与易错点: QUIC 不是不可靠 UDP,而是应用层重新实现可靠协议。
8. 常见的HTTP状态码有哪些?301/302/403等状态码的含义?
面试先答: 2xx 成功(200/201/204),3xx 重定向(301 永久、302 临时、304 未修改),4xx 客户端错误(400/401/403/404/429),5xx 服务端错误(500/502/503/504)。
详细解释: 301/308 通常允许缓存,302/303/307 对方法保持语义不同;401 表示未认证,403 是已识别但无权限。
常见追问与易错点: 认证失败不要一律返回 403;重试 5xx 需退避和幂等。
9. WebSocket和HTTP长连接的区别是什么?
面试先答: WebSocket 先 HTTP Upgrade 后建立全双工帧通信;HTTP 长连接仍是请求-响应,可通过轮询/长轮询获取服务端数据。
详细解释: WebSocket 适合实时双向消息,需要心跳、断线重连和水平扩展广播;长轮询兼容性更好但连接和延迟开销大。
常见追问与易错点: WebSocket 连接也受代理、负载均衡空闲超时影响。
7.3 DNS与网络请求流程
1. 在浏览器输入URL到页面加载完成的完整流程是什么?
面试先答: URL 解析→浏览器/系统/DNS 缓存→DNS 查询→建立 TCP 或 QUIC,TLS 握手→发送 HTTP 请求→服务端处理→响应解析、构建 DOM/CSSOM、布局绘制并加载子资源。
详细解释: 期间可能经过代理/CDN/WAF;可用 DevTools、curl、抓包分别定位 DNS、连接、TLS、TTFB 和资源阻塞。
常见追问与易错点: 页面“白屏”不一定是网络,可能是 JS 执行或渲染阻塞。
2. DNS解析的核心作用是什么?完整的DNS解析过程是怎样的?
面试先答: DNS 将域名映射为 IP。客户端先查浏览器/系统/本地缓存,再向递归 DNS 查询,递归服务器按根→顶级域→权威服务器获取并缓存记录。
详细解释: A/AAAA、CNAME、TTL、负缓存和 DNSSEC 影响结果;多地域服务常用权威 DNS 做就近/故障切换。
常见追问与易错点: CNAME 不是 IP,可能引入额外查询;TTL 到期不代表所有节点立即更新。
7.4 网络编程
1. Socket编程了解吗?
面试先答: Socket 是进程间网络通信抽象;TCP 服务端 bind/listen/accept,客户端 connect,双方 read/write,最后 close;UDP 用 sendto/recvfrom。
详细解释: 生产代码要处理半包、超时、背压、连接池、心跳和优雅关闭;Java 可用 NIO/Netty。
常见追问与易错点:
close释放的是本端资源,TCP 对端感知需 FIN/半关闭。
2. 长连接和短连接的区别是什么?
面试先答: 短连接每次请求建立并关闭,简单但握手开销大;长连接复用连接,降低延迟和端口压力,但要心跳、超时和连接治理。
详细解释: HTTP/1.1 keep-alive、HTTP/2 multiplexing、数据库连接池都是长连接思路;服务端要限制空闲和最大连接数。
常见追问与易错点: 长连接不等于永不关闭,网络设备和服务端都有 idle timeout。
3. select、poll、epoll的区别是什么?
面试先答: select 有 fd 数量上限且每次复制/遍历全部集合;poll 取消固定上限但仍遍历;epoll 在内核维护红黑树和就绪链表,事件就绪后只返回活跃 fd。
详细解释: epoll 更适合大量连接、少量活跃事件,仍可能因惊群、频繁事件和用户态处理成为瓶颈。
常见追问与易错点: epoll 不是任何负载都更快,连接数少时差异不大。
4. Epoll的底层实现原理是什么?为什么比select/poll快?
面试先答:
epoll_ctl把 fd 注册到内核红黑树,驱动事件发生时加入 ready list;epoll_wait只取就绪项,减少每次全量扫描和 fd 集合复制。详细解释: 事件回调把就绪 fd 放入链表,用户态处理后继续等待;内核与用户态仍有数据拷贝,零拷贝是另一概念。
常见追问与易错点: epoll 不能解决业务慢、线程池耗尽等问题。
5. Epoll的ET和LT模式有什么区别?
面试先答: LT 只要 fd 仍可读/写就持续通知,编程简单;ET 仅状态从未就绪变为就绪时通知,必须非阻塞并循环读到
EAGAIN。详细解释: ET 减少重复通知但更易丢事件,写事件应只在有数据待发送时关注;Java NIO Selector 语义接近 LT。
常见追问与易错点: ET 下一次未读完不会自动再次通知,必须把缓冲区排空。
6. 什么是零拷贝?原理是什么?
面试先答: 零拷贝减少用户态与内核态之间的数据复制和上下文切换,例如 Linux
sendfile、splice,JavaFileChannel.transferTo。详细解释: 传统 read+write 需磁盘→内核→用户→内核→网卡;sendfile 让内核直接把页缓存发送,实际是否完全零复制取决于网卡 DMA、TLS 和文件系统。
常见追问与易错点: 使用 TLS、压缩或修改内容时往往仍需用户态处理。
八、操作系统与 Linux
8.1 操作系统基础
1. 进程和线程的区别是什么?
面试先答: 与“三、Java 并发编程 / 3.1 线程基础 / 1. 进程和线程的区别是什么?”相同,本节保留操作系统视角的入口,完整答案见前文。
详细解释: 进程强调资源隔离,线程强调调度执行;同进程线程共享地址空间和文件描述符。Java 平台线程通常映射到 OS 原生线程,现代 JDK 还提供虚拟线程。
常见追问与易错点: 不要把线程说成完全没有切换成本,也不要把“共享内存”误解为不需要同步。
2. 进程间的通信方式有哪些?
面试先答: 与“三、Java 并发编程 / 3.1 线程基础 / 2. 进程间的通信方式有哪些?”相同,完整答案见前文;本题补充 Linux 进程间通信语境。
详细解释: 管道、FIFO、消息队列、共享内存、信号量、信号、Socket、文件和内存映射都属于常见方式。共享内存吞吐高但需要同步,Socket 可跨主机,消息队列提供缓冲。
常见追问与易错点: 信号主要用于通知,不适合传输大量业务数据。
3. 线程间的通信方式有哪些?
面试先答: 与“三、Java 并发编程 / 3.1 线程基础 / 3. 线程间的通信方式有哪些?”相同,完整答案见前文;本节从操作系统共享地址空间角度补充。
详细解释: Java 中可使用共享变量配合锁、volatile、wait/notify、Condition,也可使用阻塞队列、Future 和 CountDownLatch。JMM 要求通过 happens-before 保证可见性和有序性。
常见追问与易错点: volatile 不保证复合操作原子性,优先使用高级并发工具而不是手写等待循环。
4. 什么是进程上下文切换?和线程上下文切换的区别?
面试先答: 调度器保存当前执行状态并恢复另一个任务;进程切换还涉及地址空间/TLB,通常比同进程线程切换成本高。
详细解释: 过多切换会消耗 CPU 缓存和调度时间;用 perf、vmstat、pidstat 观察上下文切换和运行队列。
常见追问与易错点: 线程切换也可能触发内核态切换,不能绝对认为“线程零成本”。
5. 什么是写时拷贝(Copy-On-Write)?应用场景和好处是什么?
面试先答: 多方先共享只读页,首次写入时触发缺页并复制,减少初始复制成本;fork、Redis RDB、不可变集合都使用类似思想。
详细解释: 写放大和内存峰值可能在高写入时出现;Redis fork 后修改越多,COW 占用越大。
常见追问与易错点: COW 不是无复制,写入最终仍需独立内存。
6. Windows和Linux操作系统的区别是什么?
面试先答: Linux 开源、类 Unix、命令行和可裁剪性强,服务器自动化生态丰富;Windows 商业桌面生态和图形工具更完整。核心差异在权限、进程模型、文件系统和运维方式。
详细解释: Linux 一切皆文件、权限位和 cgroup/namespace 适合容器;实际选型看软件兼容、运维能力和许可证。
常见追问与易错点: 不要简单说 Linux 一定更安全/性能更高,配置和使用方式决定结果。
7. Linux操作系统的内核了解吗?
面试先答: Linux 内核负责进程调度、内存、VFS、网络栈、设备驱动和安全;用户通过系统调用进入内核,发行版在其上提供工具和服务。
详细解释: CFS 调度、页缓存、epoll、cgroup、namespace 是后端常见知识;容器共享宿主机内核。
常见追问与易错点: Ubuntu/CentOS 是发行版,不是不同内核原理;内核版本会影响特性。
8.2 Linux常用命令
1. Linux中查看当前系统所有进程的命令是什么?
面试先答:
ps aux或ps -ef查看快照,top/htop实时查看;pgrep -af java可按名称筛选。详细解释: 结合 PID、PPID、CPU、内存、启动命令定位服务;容器内的 PID 视 namespace 而定。
常见追问与易错点:
ps是瞬时快照,持续趋势用pidstat/监控系统。
2. 如何查看Linux系统的日志?
面试先答:
journalctl -u service -f查看 systemd 日志,tail -f /var/log/app.log跟踪文件,配合grep、less和时间过滤。详细解释: 先确认日志路径、轮转和权限;线上避免
cat超大文件,使用journalctl --since/--until。常见追问与易错点: 容器日志可能在 stdout,由 Docker/Kubernetes 收集,不一定存在宿主机普通文件。
3. Linux中export命令的作用是什么?
面试先答: 将 shell 变量导出为环境变量,使子进程可继承,例如
export JAVA_HOME=/opt/jdk;只对当前 shell 会话生效。详细解释: 永久配置应写 profile 或服务管理器 Environment;
printenv/env查看,unset删除。常见追问与易错点: 子进程修改不会反向影响父 shell。
4. Linux中chmod命令的作用和使用方式?
面试先答: 修改文件权限;
chmod 755 script.sh或chmod u+x script.sh,三位分别表示 owner/group/other 的读写执行。详细解释: 生产遵循最小权限,目录执行位表示可遍历;ACL、umask 会影响最终权限。
常见追问与易错点:
chmod 777是危险的粗暴修复,尤其服务配置和密钥文件。
5. Ubuntu系统中查看已安装软件包的核心命令是什么?
面试先答:
dpkg -l列出已安装包,dpkg -s package查看详情;apt list --installed也可用。详细解释:
apt管理仓库和依赖,dpkg管理本地包;排查版本用apt-cache policy。常见追问与易错点: CentOS/RHEL 使用
rpm -qa/dnf list installed,不要混淆。
6. 如何用find命令查找当前目录下所有.log文件?
面试先答:
find . -type f -name '*.log';加-maxdepth限制层级,-print0处理特殊文件名。详细解释: 删除/批处理前先单独执行确认结果;可用
-mtime、-size、-user过滤。常见追问与易错点: 通配符要加引号,避免被当前 shell 提前展开。
7. xargs命令的作用是什么?
面试先答: 把标准输入转换为命令参数,例如
find . -name '*.log' -print0 | xargs -0 grep -n error;可用-P并发但需评估顺序和资源。详细解释:
-0配合 null 分隔安全处理空格/换行文件名;大批参数会自动分批调用。常见追问与易错点: 未加
-0可能误处理特殊文件名;并发执行可能引起数据库/磁盘过载。
8. grep命令的常用用法有哪些?
面试先答:
grep -n行号、-i忽略大小写、-r递归、-E正则、-C 3上下文、-v反选;配合--include='*.log'限定文件。详细解释: 大日志优先按时间切片再 grep;输出脱敏,避免把 token/密码写到终端或工单。
常见追问与易错点: 基本正则与扩展正则语法不同;
grep返回码 1 可能只是未匹配。
9. 如何用Linux命令找出当前目录下所有.log文件里包含"error"的行,并存到error_log.txt?
面试先答:
find . -type f -name '*.log' -print0 | xargs -0 grep -nH 'error' > error_log.txt。详细解释:
-H保留文件名,-i可不区分大小写;先确认输出路径有权限并避免把结果文件本身再次匹配。常见追问与易错点: 目录无文件时 xargs 行为需用
-r(GNU);超大输出应分片或压缩。
8.3 问题排查相关
1. 如何用Linux命令排查系统性能问题?
面试先答: 先用
uptime/vmstat看总体,再用top/pidstat看 CPU,free/iostat看内存磁盘,ss/sar看网络,最后关联应用日志和指标。详细解释: 按 CPU→内存→I/O→网络链路定位,记录时间窗口和基线;容器环境还看 cgroup 限额与节点资源。
常见追问与易错点: 单一命令只能提供局部证据,不能凭 load 高就断定 CPU 问题。
2. 线上服务CPU100%如何用Linux命令排查?
面试先答:
top -H -p PID找热点线程,线程 ID 转十六进制后用jstack PID定位 Java 栈;同时看 GC、日志和近期发布。详细解释: 结合
pidstat -p、perf top区分用户代码、系统调用和 GC;限流/扩容先止血,保留现场再修复。常见追问与易错点: CPU 高不一定是死循环,也可能是频繁 GC、正则回溯或下游重试风暴。
3. 如何查看系统的内存、磁盘使用情况?
面试先答:
free -h/vmstat看内存,df -h看文件系统,du -xhd1定位目录,iostat -xz 1看磁盘 I/O。详细解释: 区分 page cache、swap、容器 limit;磁盘满还需查 inode(
df -i)和已删除但仍打开文件(lsof +L1)。常见追问与易错点:
free中 buff/cache 可回收,不要直接当作已用不可用。
九、消息队列
9.1 消息队列基础
1. 为什么要使用消息队列?消息队列的作用是什么?
面试先答: 解耦生产者与消费者、异步提速、削峰填谷和跨服务可靠传递;代价是重复、延迟、积压和运维复杂度。
详细解释: 引入 MQ 前先判断是否可接受最终一致和异步语义;消息需定义 schema、版本、超时、重试和死信。
常见追问与易错点: MQ 不是万能缓存,不能掩盖消费能力不足。
2. 什么是削峰填谷?
面试先答: 生产流量瞬时高峰先进入队列,消费者按稳定速率处理,把突发压力摊平,保护数据库和下游。
详细解释: 队列容量有限,必须监控堆积并设置最大延迟;超过容量要限流、丢弃非关键消息或降级。
常见追问与易错点: 削峰会增加端到端延迟,不适合要求同步即时结果的接口。
3. 消息队列的常见使用场景有哪些?
面试先答: 订单异步、库存/支付事件、日志采集、通知、数据同步、延迟任务和流式计算。
详细解释: 关键领域事件使用幂等消费者和可追溯消息;广播通知可用发布订阅,任务分发用消费组。
常见追问与易错点: 不能用 MQ 代替数据库事务,需事务消息/本地消息表保证发送可靠。
9.2 消息可靠性保证
1. 如何保证消息不丢失?
面试先答: 生产端确认/重试,Broker 持久化和多副本,消费端手动 ACK 且处理成功后确认;失败进入重试/死信,配合监控和对账。
详细解释: 发送与本地事务需用 outbox/事务消息;ACK 前宕机会重复,必须幂等。明确端到端至少一次/至多一次语义。
常见追问与易错点: “持久化=true”不代表副本已落盘,需看 acks、刷盘和 ISR。
2. 如何处理消息重复消费的问题?
面试先答: 默认按至少一次设计,使用业务唯一键/去重表、数据库唯一约束或状态机保证幂等;重复消息安全返回成功。
详细解释: 去重记录与业务更新放同一事务;外部调用用幂等 token。不要依赖消费者内存集合,重启会丢状态。
常见追问与易错点: MQ 的 exactly-once 通常只覆盖特定链路,不能解决外部副作用重复。
3. 如何保证消息的有序性?
面试先答: 按业务 key 路由同一分区/队列,单线程或单消费者顺序处理,生产与消费都避免并发重排;扩容会改变并行度。
详细解释: 定义全局有序还是 key 内有序,通常只保证 key 内;失败重试要阻塞后续或转补偿队列。
常见追问与易错点: 只保证生产顺序不够,重试、批量和多消费者都会打乱结果。
4. 消息积压了怎么办?
面试先答: 先确认生产突增、消费故障还是下游变慢;临时扩容消费者/批量消费、限流生产、降级非关键消息,修复后补偿和回放。
详细解释: 监控 lag、 oldest age 和失败率;扩容要受分区/队列数和下游容量限制,不能盲目加线程。
常见追问与易错点: 清空积压可能丢业务,必须按消息类型制定丢弃和重放策略。
9.3 Kafka
1. Kafka分区的目的是什么?
面试先答: 分区提供水平扩展、并行读写和局部有序;每个分区是追加日志,消息按 offset 存储。
详细解释: 分区数决定最大消费者并行度;key 相同通常哈希到同分区。过多分区增加文件、选举和恢复成本。
常见追问与易错点: Kafka 只保证分区内有序,不保证跨分区全局顺序。
2. Kafka消费者组的原理是什么?
面试先答: 同组消费者共同消费主题,一个分区同一时刻只分配给一个消费者;组通过 offset 记录进度,重平衡处理成员变化。
详细解释: 分区数小于消费者数时部分空闲;cooperative sticky 等策略可减少重平衡抖动。提交 offset 要与业务处理时机匹配。
常见追问与易错点: 不同消费者组各自独立消费,不能把组理解为广播。
3. Kafka怎么保证消息有序性?
面试先答: 业务 key 固定分区,生产端启用幂等并控制
max.in.flight,消费者单线程按 offset 处理;只承诺分区内顺序。详细解释: 重试可能让旧消息晚到,幂等生产者和事务可降低乱序;多线程消费需按 key 分片串行。
常见追问与易错点: 设置一个分区能全局有序但吞吐和可用性下降。
4. Kafka和RocketMQ的区别是什么?
面试先答: Kafka 擅长高吞吐日志/流处理、生态广;RocketMQ 原生支持延迟、事务、顺序和重试,业务消息能力更完整。两者都依赖分区/队列和副本。
详细解释: 对比要看吞吐、延迟、运维、消费模型、生态和团队经验,不能只背“Kafka 快、RocketMQ 可靠”。
常见追问与易错点: 版本和部署模式会改变特性,回答时注明 Kafka 3.x/RocketMQ 5.x。
9.4 RocketMQ
1. 为什么选用RocketMQ而不是Kafka?
面试先答: 需要延迟消息、事务消息、顺序消费和成熟重试/死信时 RocketMQ 更贴近业务;日志分析和超大吞吐则 Kafka 生态更合适。
详细解释: 评估 NameServer/Controller、存储、监控和团队维护能力;不要为少量异步通知引入过重集群。
常见追问与易错点: 选择理由应结合指标与场景,不要泛化成产品绝对优劣。
2. RocketMQ的延迟队列是怎么实现的?
面试先答: 消息写入特定延迟级别的内部 Topic,定时服务到期后转发到原目标 Topic,消费者按普通消息消费。
详细解释: 传统版本延迟级别固定,RocketMQ 5.x 支持更丰富的定时消息能力(以部署版本为准);延迟期间仍占用 Broker 资源。
常见追问与易错点: 延迟消息不是精确计时器,故障和负载会带来误差。
3. RocketMQ事务消息的原理是什么?
面试先答: 先发送 half 消息,执行本地事务,再向 Broker 提交 commit/rollback;状态未知时 Broker 回查生产者,最终决定可见性。
详细解释: 本地事务必须幂等、可查询且与业务状态一致;回查服务不可用会导致消息长期悬挂,需监控和人工补偿。
常见追问与易错点: 事务消息只协调消息与本地事务,不能自动覆盖多个外部资源。
9.5 RabbitMQ
1. RabbitMQ的镜像队列机制是如何保证消息高可用的?
面试先答: 旧版镜像队列把队列数据同步到多个节点,主节点故障时从节点接管;RabbitMQ 3.8+ 推荐 quorum queue(基于 Raft)提高一致性。
详细解释: 发布确认、持久化和副本落盘共同决定可靠性;quorum 队列写放大更高,需按吞吐和容灾选择。
常见追问与易错点: 镜像并非跨机房强一致,网络分区和脑裂策略要明确。
2. 使用RabbitMQ时,如何解决消息丢失和重复消费问题?
面试先答: 发布 confirm、持久化 exchange/queue/message,消费成功后 ACK;失败重回队列/死信。消费者按业务 ID 做幂等。
详细解释: 使用 mandatory/return 处理无路由消息,设置 prefetch 控制未确认数量;ACK 前宕机会重投,重复是正常语义。
常见追问与易错点: autoAck 会在业务处理前确认,异常时直接丢消息。
十、微服务与分布式系统
10.1 分布式基础理论
1. CAP定理是什么?
面试先答: 分区容错 P 无法避免时,一致性 C 与可用性 A 只能取舍;CP 牺牲部分可用保证一致,AP 接受暂时不一致。
详细解释: 见本文件 MySQL 5.7.2,微服务注册中心、配置中心要按故障语义选择;CAP 不是平时只能二选一。
常见追问与易错点: 网络分区期间仍可能局部可用,需说明“对哪些请求”。
2. BASE理论是什么?
面试先答: 基本可用、软状态、最终一致,允许短暂不一致换取可用性和扩展性;通过重试、补偿、对账收敛。
详细解释: BASE 不是放弃一致性,而是把一致性从同步强约束改为业务可接受的异步约束。
常见追问与易错点: 最终一致必须有最大收敛时间和异常告警,不能无限拖延。
10.2 微服务架构
1. 什么是微服务?微服务架构的优缺点是什么?
面试先答: 微服务按业务边界拆成可独立部署的小服务,通过网络协作。优点是自治、弹性扩展和团队并行;缺点是分布式复杂度、运维成本和数据一致性难题。
详细解释: 服务边界以领域模型和变更频率为依据,避免过度拆分;统一治理日志、配置、链路、发布和安全。
常见追问与易错点: 微服务不是“类越小越好”,单体在早期可能更合适。
2. 微服务架构遇到的常见问题及解决方案?
面试先答: 网络超时/重试风暴用超时、限流、熔断;数据一致性用事件/事务消息;调用排查用链路追踪;发布用灰度和回滚。
详细解释: 还需解决服务发现、配置、幂等、版本兼容、热点和容量;每个治理措施都要有指标和压测。
常见追问与易错点: 重试必须有预算且只对幂等操作使用,否则会放大故障。
10.3 微服务核心组件
1. 什么是服务注册与发现?原理是什么?
面试先答: 服务启动向注册中心登记实例和健康信息,消费者订阅列表并通过心跳/探活剔除故障,再结合负载均衡调用。
详细解释: 注册中心需处理临时/持久实例、缓存和网络分区;客户端应本地缓存最后可用列表并设置保护阈值。
常见追问与易错点: 注册中心不可用时已有调用应继续使用缓存,不能让全站同步失效。
2. Nacos和Eureka的区别是什么?
面试先答: Nacos 同时提供注册发现和配置中心,支持 CP/AP 模式;Eureka 主要服务发现,强调 AP 和自我保护,Spring Cloud Netflix 生态已进入维护阶段。
详细解释: 选型看版本、部署和团队生态;Nacos 2.x 长连接降低推送延迟,Eureka 客户端缓存机制更简单。
常见追问与易错点: 不要把 Nacos 永远说成 CP,临时实例默认 AP,持久实例可选 CP。
3. 配置中心的作用是什么?原理是什么?
面试先答: 统一管理、版本化和动态推送配置,避免重新发布;客户端启动拉取、监听变更并刷新受支持的 Bean。
详细解释: 配置需分环境、加密、审计和回滚;动态刷新要控制生效范围,数据库连接等关键配置通常需重启。
常见追问与易错点: 配置中心挂掉时客户端应使用本地快照,不能每次请求实时依赖它。
4. API网关的作用是什么?
面试先答: 统一入口,负责路由、认证、限流、熔断、协议转换、灰度和审计,隐藏内部服务拓扑。
详细解释: 网关要无状态可水平扩展,避免承载复杂业务;大文件/长连接需单独优化和旁路。
常见追问与易错点: 网关是治理边界,不是万能业务层,单点故障需多实例和快速回滚。
5. 常见的负载均衡算法有哪些?轮询算法的核心缺陷是什么?
面试先答: 轮询、加权轮询、随机、最少连接、IP/一致性哈希和带延迟/错误率的自适应算法;普通轮询忽略实例容量、连接数和健康差异。
详细解释: 权重需动态调整并防止热点;一致性哈希适合会话/缓存但扩缩容仍有迁移。
常见追问与易错点: 只按 CPU 分配不一定准确,需结合请求耗时和下游容量。
6. Dubbo的原理是什么?SPI机制是什么?
面试先答: Dubbo 通过代理、序列化、网络协议和注册中心完成 RPC 调用,支持集群容错、路由和负载均衡;SPI 是按接口配置动态加载扩展实现。
详细解释: 调用链为代理→Filter→集群→路由/负载→协议→网络;Dubbo SPI 支持自适应扩展和 IOC 注入,和 JDK SPI 的全量加载不同。
常见追问与易错点: 超时、重试、版本和序列化兼容必须明确,不能只说“比 HTTP 快”。
7. RPC和HTTP的区别是什么?
面试先答: RPC 是调用抽象,常用二进制协议、IDL 和连接复用追求低延迟;HTTP 是通用应用协议、工具和可观测性好。RPC 也可能基于 HTTP/2。
详细解释: 内部高频调用可选 gRPC/Dubbo,外部开放 API 常用 REST/HTTP;选择看兼容性、治理和团队成本。
常见追问与易错点: RPC 不是传输层协议,HTTP 也不必然低性能。
8. gRPC的原理是什么?
面试先答: gRPC 用 Protocol Buffers 定义接口和消息,基于 HTTP/2 多路复用传输,生成客户端/服务端 stub,支持流式调用和 metadata。
详细解释: HTTP/2 头压缩、二进制序列化降低开销;需处理 deadline、状态码、TLS、负载均衡和跨语言版本兼容。
常见追问与易错点: 浏览器直连 gRPC 需 gRPC-Web,不能假设普通 fetch 直接调用。
9. Spring Cloud的核心组件有哪些?
面试先答: 常见有 Config、Discovery(Nacos/Eureka)、Gateway、OpenFeign、LoadBalancer、CircuitBreaker、Stream、Sleuth/OTel 等;组件随 Spring Cloud 版本变化。
详细解释: Spring Cloud 2023+ 与 Boot 3 对应,Hystrix/Ribbon 已被 Resilience4j/Spring Cloud LoadBalancer 替代;以 BOM 管理兼容版本。
常见追问与易错点: 不要把已停更组件当新项目默认选型。
10. Feign和Ribbon的区别是什么?
面试先答: Feign 是声明式 HTTP 客户端;Ribbon 是旧版客户端负载均衡器,现由 Spring Cloud LoadBalancer 替代。Feign 可集成负载均衡、重试和熔断。
详细解释: Spring Cloud OpenFeign 通过代理生成请求,需配置超时、编码器和错误解码;Boot 3 项目优先使用 LoadBalancer。
常见追问与易错点: Feign 本身不是注册中心,也不等于自动重试安全。
11. Hystrix和Sentinel的区别是什么?
面试先答: Hystrix 提供线程/信号量隔离、熔断和降级但已停止维护;Sentinel 更侧重流量控制、系统自适应保护和规则动态配置,支持熔断降级。
详细解释: 新项目可用 Spring Cloud CircuitBreaker+Resilience4j,或按生态选择 Sentinel;规则持久化、集群限流和控制台高可用需另建。
常见追问与易错点: 熔断是失败保护,限流是流量保护,两者指标和动作不同。
10.4 分布式事务
1. 常见的分布式事务解决方案有哪些?
面试先答: 2PC/3PC、TCC、Saga、本地消息表、可靠消息/事务消息和 Seata AT;优先最终一致,强一致才选高成本方案。
详细解释: 依据跨服务边界、补偿能力、延迟和隔离要求选型;所有方案都需幂等、重试、超时、监控和对账。
常见追问与易错点: 见本文件 MySQL 5.7,概念相同但微服务更关注跨服务接口和补偿。
2. 2PC、3PC、TCC、SAGA、本地消息表、事务消息的区别和适用场景?
面试先答: 2PC/3PC 偏协议强一致但阻塞;TCC 业务侵入大但可控;Saga 长流程补偿;本地消息表可靠投递;事务消息绑定单库事务与消息。按一致性和开发成本取舍。
详细解释: 2PC 适合短事务;TCC 适合可预留资源;Saga 适合可补偿步骤;消息方案适合最终一致事件。必须定义失败补偿和人工兜底。
常见追问与易错点: 没有一种方案同时做到强一致、高可用、低延迟、零侵入。
10.5 分布式ID
1. 分布式ID生成器怎么实现?
面试先答: 方案有数据库号段、Redis 原子递增、UUID、雪花算法和中心化发号器;要求全局唯一、趋势递增、可用、可解析性按业务取舍。
详细解释: 号段减少网络但要处理缓存耗尽;UUID 随机写入数据库页不友好;ID 服务需高可用、时钟和回拨监控。
常见追问与易错点: 全局唯一不等于绝对有序,跨机房顺序通常只能近似。
2. Snowflake雪花算法的原理是什么?
面试先答: 64 位 ID 通常由符号位、时间戳、机器/数据中心位、序列号组成,同毫秒内序列递增,趋势有序且本地生成。
详细解释: 经典 Twitter 方案是 41+10+12 位,时间回拨会导致重复/阻塞;可用 NTP 监控、等待、备用 worker 或改用号段。
常见追问与易错点: 机器号分配和时钟回拨是生产核心风险,不能只背位数。
10.6 高可用与高并发设计
1. 如何设计一个高可用的系统?
面试先答: 消除单点、服务多实例、数据多副本、健康检查和自动故障切换;配合超时、重试、熔断、降级、限流、灰度发布和灾备演练。
详细解释: 先定义 SLO/RTO/RPO,再做容量和故障域设计;可用性依赖最短板,监控告警和应急预案同样重要。
常见追问与易错点: 多副本不等于高可用,脑裂、数据丢失和切换时间要量化。
2. 如何设计一个高并发的系统?
面试先答: 无状态水平扩展、缓存/读写分离、异步削峰、分库分表、连接池和限流;热点和下游容量决定瓶颈。
详细解释: 用压测找拐点,按 QPS、P99、吞吐和资源利用率扩容;避免锁竞争、深分页和大事务。
常见追问与易错点: 先缓存再扩容可能掩盖一致性问题,必须设计失效和降级。
3. 什么是服务雪崩?如何避免?
面试先答: 一个服务故障通过同步调用链扩散,导致线程池、连接池耗尽;用超时、隔离、熔断、限流、降级、舱壁和异步解耦。
详细解释: 重试需指数退避和总预算;熔断恢复采用半开探测,依赖服务应设置独立线程池和容量上限。
常见追问与易错点: 无脑重试是雪崩放大器。
4. 什么是熔断和降级?区别是什么?
面试先答: 熔断根据错误/延迟阈值暂时拒绝调用,保护系统;降级是在资源不足或故障时返回缓存、默认值或简化功能。可组合使用。
详细解释: 熔断器通常 closed→open→half-open;降级策略必须明确业务可接受结果和恢复方式。
常见追问与易错点: 限流、熔断、降级不是同义词,指标和触发方不同。
5. 常见的限流算法有哪些?令牌桶和漏桶的区别是什么?
面试先答: 固定/滑动窗口、漏桶、令牌桶和并发信号量。令牌桶允许一定突发,漏桶以固定速率平滑输出。
详细解释: 令牌桶适合 API 流量,参数为速率和容量;漏桶适合严格整形。分布式限流需原子计数和时钟一致。
常见追问与易错点: 窗口算法边界突刺明显,需滑动窗口或令牌桶改善。
6. 如何设计接口限流系统?
面试先答: 明确维度(租户/用户/IP/接口)、算法和配额,网关快速拦截,服务内再做并发隔离;规则动态下发并监控命中、拒绝和误伤。
详细解释: Redis/Lua 实现共享计数,单机本地限流兜底;返回 429 和 Retry-After,重要请求进入队列。
常见追问与易错点: 代理层 IP 可能被伪造,鉴权后优先按用户/租户限流。
7. 什么是接口幂等性?如何保证接口幂等性?
面试先答: 相同请求执行一次或多次结果一致;用业务唯一号、幂等表/唯一索引、状态机、幂等 token 和去重缓存实现。
详细解释: 幂等记录与业务写入同一事务;外部副作用用请求 ID、查询确认和补偿。GET 天然幂等不代表无副作用。
常见追问与易错点: 分布式锁只能降低并发,不能替代持久化幂等约束。
8. 分布式定时任务下,如何保证任务执行的幂等性?
面试先答: 任务实例抢占锁/租约后执行,业务以任务 ID+分片键做唯一约束和状态机;失败可重试,超时由补偿扫描接管。
详细解释: 调度锁和业务幂等要分开,锁失效时 fencing token 防旧实例写入;记录执行日志、心跳和最大运行时长。
常见追问与易错点: 仅依赖调度框架“只执行一次”不可靠,宕机/重平衡都会重跑。
十一、Spring 全家桶与常用框架
11.1 Spring核心
1. Spring的核心特性是什么?
面试先答: IOC/DI 管理对象,AOP 解耦横切逻辑,声明式事务和丰富生态;核心容器通过 BeanDefinition 创建、装配和生命周期管理。
详细解释: Spring 6/Boot 3 要求 Java 17+、jakarta 包;模块化按需使用,避免把容器能力当业务逻辑。
常见追问与易错点: Spring 是框架,不等于 Spring Boot;Boot 提供约定和自动配置。
2. 什么是IOC?原理是什么?
面试先答: 控制反转把对象创建和依赖关系交给容器,应用通过构造器/Setter/字段注入获取协作对象;容器依据 BeanDefinition 反射或工厂创建 Bean。
详细解释:
ApplicationContext扩展资源、事件和国际化;推荐构造器注入,便于不可变和测试。BeanFactoryPostProcessor 可修改定义。常见追问与易错点: IOC 是思想,DI 是实现方式;容器管理的 Bean 才享有生命周期和 AOP。
3. 什么是AOP?原理是什么?
面试先答: AOP 把日志、事务、权限等横切逻辑织入目标方法;Spring 默认使用 JDK 动态代理或 CGLIB 子类代理。
详细解释: 切点匹配方法,拦截器链按顺序执行;代理只能拦截经过代理对象的调用,编译期 AspectJ 可织入更多场景。
常见追问与易错点: 同类内部
this.method()不经过代理,导致事务/切面失效。
4. Spring Bean的生命周期是什么?
面试先答: 实例化→属性注入→Aware 回调→BeanPostProcessor 前置→初始化方法/@PostConstruct→后置处理(生成代理)→使用→销毁回调。
详细解释:
InitializingBean、init-method、@PreDestroy是不同扩展点;原型 Bean 默认不由容器自动销毁。常见追问与易错点: AOP 代理通常在后置处理器阶段生成,不能在构造器中调用依赖业务。
5. Spring Bean的作用域有哪些?默认是哪种?
面试先答: singleton(默认)、prototype、request、session、application、websocket;Web 作用域需 WebApplicationContext 和作用域代理。
详细解释: singleton 是容器内单实例而非 JVM 全局;prototype 每次获取新对象,但由容器创建后生命周期管理有限。
常见追问与易错点: singleton Bean 存可变成员会有并发问题,见下一题。
6. Spring Bean是线程安全的吗?如何保证线程安全?
面试先答: Spring 不自动保证线程安全;singleton 多线程共享,尽量设计为无状态,状态放局部变量/线程安全容器或外部存储。
详细解释: 需要共享状态时用锁、原子类或数据库/Redis CAS,并控制粒度;Web 请求对象通常是线程隔离的。
常见追问与易错点:
@Service默认单例不是“线程安全”,字段注入的可变集合常是隐患。
7. @Autowired和@Resource的区别是什么?
面试先答:
@Autowired是 Spring 注解,按类型优先,可配@Qualifier;@Resource是 Jakarta 注解,默认按名称再按类型。都支持字段/Setter,构造器推荐@Autowired(单构造器可省略)。详细解释: 同类型多个 Bean 时明确
@Qualifier或@Primary;Spring 6 使用jakarta.annotation.Resource,旧 Spring 使用javax。常见追问与易错点: 不要仅靠字段名解决歧义,重构会破坏注入。
8. Spring如何解决循环依赖?
面试先答: 单例 setter/字段注入可通过三级缓存提前暴露工厂对象,创建 A 时先暴露引用再注入 B;构造器循环依赖无法自动解决。
详细解释: 三级缓存支持 AOP 早期代理,二级存早期引用;
allowCircularReferences在新版本可配置关闭。优先重构职责、改构造器或事件解耦。常见追问与易错点: prototype 循环通常无法解决;提前暴露对象可能看到未初始化状态。
9. Spring事务的传播行为有哪些?
面试先答: REQUIRED(默认)、REQUIRES_NEW、NESTED、SUPPORTS、NOT_SUPPORTED、MANDATORY、NEVER;核心是新建、加入、挂起或拒绝现有事务。
详细解释:
REQUIRES_NEW独立提交但占用额外连接;NESTED 依赖保存点且通常同一物理事务。传播与隔离级别是两个维度。常见追问与易错点: 内部自调用不经过代理,传播注解不会生效。
10. @Transactional注解的原理是什么?失效场景有哪些?
面试先答: Spring 通过事务拦截器代理方法,进入时开启/加入事务,正常提交、异常回滚;失效常因自调用、非 public、异常被吞、检查异常未配置回滚、对象非容器 Bean 或代理被绕过。
详细解释: 默认对 RuntimeException/Error 回滚,检查异常需
rollbackFor;多数据源需正确事务管理器。事务边界应放 service,避免远程调用包在长事务中。常见追问与易错点:
readOnly=true是优化提示而非禁止写入的绝对约束;异步线程不会自动继承事务。
11.2 Spring Boot
1. Spring Boot自动装配原理是什么?
面试先答:
@SpringBootApplication组合配置、组件扫描和@EnableAutoConfiguration;Boot 根据 classpath、配置属性和条件注解加载AutoConfiguration,注册默认 Bean。详细解释: Boot 3 使用
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,旧版常见spring.factories;条件不满足时不装配,可用--debug查看报告。常见追问与易错点: 自动装配可被用户 Bean、
@ConditionalOnMissingBean和顺序覆盖,不是无条件创建。
2. Spring Boot Starter是什么?
面试先答: Starter 是一组约定依赖和自动配置的聚合包,如
spring-boot-starter-web,简化版本管理和开箱配置。详细解释: 自定义 Starter 通常拆
-autoconfigure与依赖包,提供@AutoConfiguration、配置属性和条件;BOM 统一兼容版本。常见追问与易错点: Starter 本身不一定包含实现代码,可能只是依赖集合。
3. Spring Boot常用的注解有哪些?
面试先答: 启动/配置:
@SpringBootApplication、@Configuration、@Bean、@ConfigurationProperties;Web:@RestController、@RequestMapping;事务/校验:@Transactional、@Validated;条件:@Profile、@ConditionalOnProperty。详细解释: 注解最终通过容器注册、Bean 后置处理器或 MVC 映射器生效;先理解机制再背清单。
常见追问与易错点:
@Component、@Service、@Repository主要是语义分层,底层都可被组件扫描。
11.3 Spring MVC
1. Spring MVC的工作流程是什么?
面试先答: 请求到 DispatcherServlet→HandlerMapping 找控制器→HandlerAdapter 调用→参数解析/校验→返回值处理器序列化或视图渲染→异常解析器处理错误。
详细解释: 拦截器位于控制器前后,Filter 在 Servlet 层更早;Boot 通过消息转换器支持 JSON。统一异常和 trace ID 要在边界处理。
常见追问与易错点:
@Controller默认返回视图,@RestController等于@Controller+@ResponseBody。
2. Tomcat的Connector组件的核心职责是什么?
面试先答: Connector 监听端口、接收连接、解析 HTTP/协议并把请求交给容器;包含 Endpoint、线程池、Protocol、Adapter 等部分。
详细解释: NIO/NIO2/APR 影响 I/O 模型,线程池和连接超时决定并发;Boot 3 默认 Tomcat 10.1(Jakarta)。
常见追问与易错点: Connector 负责网络接入,业务 Servlet 处理在 Container 层,不要混为一谈。
11.4 MyBatis
1. MyBatis中#{}和${}的区别是什么?
面试先答:
#{}使用预编译参数占位符,防 SQL 注入并正确处理类型;${}直接字符串拼接,仅用于受控的表名/排序片段。详细解释: 动态列名应白名单映射后再拼接,不能把用户输入直接传
${};#{}会产生?并复用执行计划。常见追问与易错点:
${}不是“更快”,安全风险和计划缓存问题更大。
2. MyBatis的一级缓存和二级缓存是什么?
面试先答: 一级缓存默认开启,作用域通常 SqlSession;二级缓存按 Mapper/namespace 跨 SqlSession,共享需显式配置且事务提交才刷新。
详细解释: 更新/提交会清理相关缓存;多实例部署、外部修改数据库会造成脏读,生产常用 Redis 等统一缓存并明确失效。
常见追问与易错点: 二级缓存不是全局缓存,跨 namespace 关联更新难保证一致。
3. MyBatis的插件原理是什么?
面试先答: 插件实现
Interceptor,通过动态代理拦截 Executor、StatementHandler、ParameterHandler、ResultSetHandler 的指定方法,修改参数或执行流程。详细解释:
@Intercepts/@Signature定义拦截点,多个插件按注册顺序形成代理链;分页插件应使用方言和参数化 SQL。常见追问与易错点: 插件能力强但易影响所有 SQL,必须限制拦截范围并做性能测试。
4. MyBatis一级缓存的作用域是哪里?默认是否开启?
面试先答: 默认开启,作用域是同一个
SqlSession;同一会话相同查询可复用结果,执行更新、提交、回滚或clearCache会清空。详细解释: Spring 管理 Mapper 时一个事务通常绑定一个 SqlSession;不同事务/线程不共享一级缓存。
常见追问与易错点: 不要把一级缓存误认为 JVM 全局缓存;关闭可设
localCacheScope=STATEMENT。
11.5 Spring AI
1. 介绍一下Spring AI这个框架?
面试先答: Spring AI 是 Spring 生态的 AI 应用抽象,统一 ChatClient、模型、Embedding、Vector Store、Advisor 和 Tool Calling 接口,便于接入不同模型。
详细解释: 以 Spring Boot 3 为基础,支持同步/流式响应、结构化输出、RAG 和记忆;生产需处理密钥、超时、限流、成本和敏感数据。
常见追问与易错点: Spring AI 版本迭代快,具体类名随 milestone 变化,应以项目 BOM 文档为准。
2. 用Spring AI写一个Agent的过程大概是什么样的?
面试先答: 定义目标与工具接口→配置 ChatClient/模型→把工具注册为
@Tool→模型决定调用工具→执行并把结果回传→循环直到得到答案,增加权限、超时和审计。详细解释: 工具调用需参数校验、幂等和沙箱;RAG 场景先检索再生成,流式输出要处理断线和取消。限制最大步骤防止死循环。
常见追问与易错点: Agent 不是让模型任意执行代码,工具权限和提示注入防护必须在服务端强制。
十二、设计模式
1. 常用的设计模式有哪些?分别解决什么问题?
面试先答: 常用模式可按创建型、结构型、行为型分类。创建型(单例、工厂、建造者)解决对象创建和生命周期;结构型(代理、装饰器、适配器、外观)解决组合和接口兼容;行为型(策略、模板方法、观察者、责任链、命令)解决职责分配和对象协作。模式不是越多越好,选择依据是变化点、复杂度和可测试性。
详细解释: 单例确保一个进程内唯一实例;工厂封装具体实现选择;建造者分步构造复杂对象。代理在不改目标类的情况下增加鉴权、事务、日志等横切逻辑,装饰器可动态叠加行为,适配器把不兼容接口转换为目标接口。策略把可替换算法封装成接口,模板方法固定流程骨架,观察者用于事件通知,责任链让请求沿处理器链传递。Spring 中 BeanFactory/FactoryBean、AOP 代理、@Transactional、事件监听、HandlerInterceptor 都体现这些思想。
常见追问与易错点: 设计模式不是框架 API 的同义词;不要为了“套模式”引入过度抽象。回答时要说明业务变化点、替代方案和测试收益。
2. 单例模式的实现方式有哪些?应用场景是什么?
面试先答: 常见实现有饿汉式、懒汉式加锁、双重检查锁、静态内部类和枚举。优先推荐枚举或静态内部类;它们线程安全、延迟初始化(枚举除外)且实现简单。适合无状态配置、连接池管理器、限流器等进程级共享对象,不适合需要多租户隔离或频繁变更状态的对象。
详细解释: 饿汉式在类初始化时创建,利用 JVM 类初始化的线程安全;懒汉式需同步保证并发安全。静态内部类只有调用 getInstance 时才初始化 Holder。枚举由 JVM 保证单实例,并能抵抗反射和反序列化破坏。Spring 默认单例是“容器范围单例”,不等价于 JVM 全局单例,多个 ApplicationContext 仍可能各有一份。
常见追问与易错点: 单例实例内部可变状态会带来并发问题;序列化需 readResolve(枚举无需);集群中单例只在单 JVM 内有效。
3. 手写双重检验锁(DCL)实现单例模式?
面试先答: 实例字段必须使用 volatile,第一次判空减少无竞争时的加锁,第二次判空防止多个线程先后创建。volatile 禁止重排序,避免其他线程看到未完成初始化的对象。
详细解释:
public final class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
Singleton r = instance;
if (r == null) {
synchronized (Singleton.class) {
r = instance;
if (r == null) {
r = new Singleton();
instance = r;
}
}
}
return r;
}
}创建对象包含分配、初始化、引用赋值三个步骤;没有 volatile 时可能发生“先赋引用、后初始化”,读线程拿到半初始化对象。局部变量 r 只减少重复读取 volatile 的开销,非必需但常用。
常见追问与易错点: 构造器不能公开;反射仍可破坏普通 DCL,序列化会生成新实例;枚举/静态内部类通常更推荐。
4. 工厂模式的应用场景是什么?
面试先答: 当创建逻辑依赖类型、配置或环境,且调用方不应依赖具体类时使用工厂。简单工厂集中分支,工厂方法让子类决定产品,抽象工厂创建同一产品族。
详细解释: 例如根据支付渠道返回 Payment 实现,新增渠道只扩展实现和注册表,不修改业务流程。Spring 的 BeanFactory、FactoryBean,JDBC 的 DriverManager 都是工厂思想。实现时可用 Map<String, Supplier<? extends Payment>> 注册,避免巨大 if-else。工厂应专注创建和配置,业务编排交给服务层。
常见追问与易错点: 工厂不等于静态工具类;产品类型很少且无变化时直接构造更简单。抽象工厂容易导致接口和实现数量膨胀。
5. 代理模式的应用场景是什么?
面试先答: 代理为目标对象增加访问控制、事务、缓存、日志、远程调用等横切能力,调用方仍面向同一接口。静态代理编译期确定,JDK 动态代理要求接口,CGLIB/Byte Buddy 通过子类或字节码增强。
详细解释: Spring AOP 默认使用 JDK 代理(目标实现接口时)或 CGLIB;@Transactional、方法耗时统计都在代理拦截器中完成。RPC 客户端代理把本地方法调用编码成网络请求。代理还可实现懒加载和熔断。注意自调用不会经过 Spring 代理,需拆分 Bean 或通过代理对象调用。
常见追问与易错点: JDK 代理只能代理接口;final 类/方法不能被 CGLIB 覆盖;代理链顺序影响事务和异常处理;不要在代理中吞掉异常。
6. 观察者模式的应用场景是什么?
面试先答: 一个主题状态变化时通知多个订阅者,降低发布者与消费者耦合。同步观察者适合本地轻量事件,异步事件总线或 MQ 适合解耦耗时任务并提供重试。
详细解释: Spring ApplicationEventPublisher、Kafka 消费组都是观察者思想。设计时定义事件不可变载荷、订阅者异常隔离和幂等键。同步发布会把延迟和失败传播给主流程;异步发布要考虑消息持久化、顺序、重复和最终一致性。观察者数量很大时使用 topic/分区,而不是在主题对象里维护海量回调列表。
常见追问与易错点: 观察者模式本身不保证可靠投递;事件顺序和事务边界要明确;监听器泄漏会导致内存泄漏。
7. 你的项目中使用了哪些设计模式?为什么选择这些模式?
面试先答(虚构示例): 在“校园二手交易平台”中,用策略+工厂抽象支付渠道,用模板方法统一订单状态流转,用代理实现权限和审计,用观察者发布订单事件。选择依据是渠道和规则会变化、审计是横切需求、异步通知不应阻塞下单。
详细解释: PaymentStrategy 只负责支付动作,PaymentFactory 根据渠道配置返回策略;订单服务模板固定校验、扣库存、落库、发事件顺序。AOP 代理记录操作者、请求号和耗时;事务提交后通过 Outbox/消息投递订单事件。每个模式都有可替代方案:渠道少时直接分支更简单,事件量小时可用 Spring 本地事件。
常见追问与易错点: 必须说清自己负责的代码、指标和取舍,不能只背模式名;模式应服务于变化点,避免为了展示而设计。
十三、数据结构与算法
13.1 基础算法概念
1. 常见排序算法的时间复杂度、空间复杂度对比?
面试先答: 冒泡/选择/插入平均 O(n²);希尔依赖增量;归并稳定 O(n log n)、额外 O(n);堆排序 O(n log n)、O(1) 额外空间;快速排序平均 O(n log n)、最坏 O(n²),递归栈平均 O(log n);计数/桶/基数在键范围受限时可近似线性。稳定性和是否原地同样重要。
详细解释: 插入排序对近乎有序数据很快;归并适合外部排序和链表;快排缓存友好但要随机化或三数取中避免退化;堆排序上界稳定但常数较大。Java Arrays.sort 对对象使用 TimSort(稳定),对基本类型使用双轴快排等实现,具体版本可能变化。
常见追问与易错点: 空间复杂度要说明是否计递归栈;“O(1)空间”不代表没有输入数组修改;稳定排序指相等元素相对顺序保持。
2. 二分查找的原理和实现?
面试先答: 在有序且可随机访问的数据上,每次比较把区间缩小一半,复杂度 O(log n)。用 mid = left + (right-left)/2 防溢出,并明确是找任意命中、左边界还是右边界。
详细解释:
static int lowerBound(int[] a, int target) {
int l = 0, r = a.length; // [l, r)
while (l < r) {
int m = l + (r - l) / 2;
if (a[m] < target) l = m + 1;
else r = m;
}
return l; // 可能等于 n
}右边界可改为查找 target + 1 的 lowerBound 再减一,并注意整数溢出。循环不变量是区间外元素已确定不满足条件。
常见追问与易错点: 数组为空、target 不存在、重复元素、边界 r 取 n 还是 n-1;链表不适合二分。
3. 动态规划的解题思路是什么?
面试先答: 定义状态、写出状态转移、初始化边界、确定遍历顺序,最后检查是否可压缩空间。DP 依赖最优子结构和重叠子问题,状态必须包含影响未来决策的全部信息。
详细解释: 以 0/1 背包为例,dp[j] 表示容量 j 的最大价值,转移 dp[j]=max(dp[j],dp[j-w]+v),容量倒序避免同一物品重复使用。区间 DP、树形 DP、状态压缩和单调队列优化都是常见变体。先写递归+记忆化通常更易验证,再改迭代。
常见追问与易错点: 初始化“不可达”状态不能默认为 0;遍历方向决定 0/1 与完全背包;状态过大时需分析时间、空间和剪枝。
4. 回溯算法的解题思路是什么?
面试先答: 回溯是在决策树上深度优先搜索:选择、递归、撤销选择。通过排序、剪枝、去重和限制候选范围降低指数搜索。
详细解释: 全排列用 used[],组合用起始下标避免重复,N 皇后用列/对角线集合快速判断。递归参数应表达当前路径、候选起点和终止条件;恢复现场必须放在递归返回后。若子问题有大量重复状态,应改用记忆化 DP。
常见追问与易错点: path 结果要复制而不是直接加入可变列表;同层去重与同支去重不同;剪枝要证明不会删掉合法解。
5. 贪心算法的适用场景是什么?
面试先答: 每一步选择当前看起来最优,并且能证明这种选择不会影响全局最优时使用。典型有区间调度、最小生成树、Huffman、部分背包;0/1 背包一般不能直接贪心。
详细解释: 区间调度按结束时间升序选择,交换论证说明可得到最多不重叠区间;Kruskal 每次选最小安全边,依赖割性质。使用前要给出贪心选择性质或交换证明,不能只凭样例验证。无证明时优先考虑 DP、搜索或最短路。
常见追问与易错点: 局部最优不等于全局最优;排序条件必须与证明一致;注意相同端点和空输入。
6. BFS和DFS的区别是什么?
面试先答: BFS 用队列按层扩展,适合无权图最短路,空间约 O(V);DFS 用栈/递归深入,适合连通性、拓扑、回溯,空间为递归深度或栈大小。图遍历都要 visited 防环。
详细解释: 网格最短步数应 BFS,首次到达即最短;DFS 可求所有路径但可能指数爆炸。递归 DFS 可能栈溢出,生产代码可改显式栈。带权图不能用普通 BFS,应使用 0-1 BFS 或 Dijkstra。
常见追问与易错点: BFS 队列入队时标记访问避免重复入队;DFS 回溯时是否撤销 visited 取决于求连通分量还是枚举路径。
13.2 手撕算法题汇总
以下实现均为 Java 17 可编译风格;方法默认不修改题目未授权的数据结构。
1. 手写堆排序
面试先答: 建大根堆后反复把堆顶交换到数组尾部并下沉,原地 O(n log n),不稳定。
详细解释:
static void heapSort(int[] a) {
for (int i = a.length / 2 - 1; i >= 0; i--) siftDown(a, i, a.length);
for (int end = a.length - 1; end > 0; end--) {
int t=a[0]; a[0]=a[end]; a[end]=t; siftDown(a,0,end);
}
}
static void siftDown(int[] a,int i,int n){
while (2*i+1<n){ int c=2*i+1; if(c+1<n&&a[c+1]>a[c])c++; if(a[i]>=a[c])break;
int t=a[i];a[i]=a[c];a[c]=t;i=c; }
}常见追问与易错点: 建堆是 O(n);子节点下标 2*i+1;空数组和单元素无需特殊处理。
2. 手写快速排序
面试先答: 分区使基准左侧不大于、右侧不小于,再递归两边;随机基准可降低最坏退化概率,平均 O(n log n),最坏 O(n²)。
详细解释:
static void quickSort(int[] a,int l,int r){
if(l>=r)return; int i=l,j=r,p=a[l+(r-l)/2];
while(i<=j){ while(a[i]<p)i++; while(a[j]>p)j++; if(i<=j){int t=a[i];a[i]=a[j];a[j]=t;i++;j--;}}
if(l<j)quickSort(a,l,j); if(i<r)quickSort(a,i,r);
}常见追问与易错点: 循环条件和边界移动必须配套,否则重复值可能死循环;深度过大可对较小区间递归、大区间迭代。
3. 手写归并排序
面试先答: 分治递归排序左右半区,再线性合并;稳定 O(n log n),辅助数组 O(n)。
详细解释:
static void mergeSort(int[] a){ int[] tmp=new int[a.length]; mergeSort(a,tmp,0,a.length-1); }
static void mergeSort(int[]a,int[]t,int l,int r){ if(l>=r)return; int m=l+(r-l)/2; mergeSort(a,t,l,m);mergeSort(a,t,m+1,r);
int i=l,j=m+1,k=l; while(i<=m&&j<=r)t[k++]=a[i]<=a[j]?a[i++]:a[j++]; while(i<=m)t[k++]=a[i++];while(j<=r)t[k++]=a[j++];
System.arraycopy(t,l,a,l,r-l+1); }常见追问与易错点: 比较用 <= 才保持稳定;辅助数组可复用避免每层分配;空数组入口安全。
4. 反转链表(K个一组翻转链表)
面试先答: 先检查剩余节点是否达到 k 个,再原地反转这一段并递归/迭代处理后续,时间 O(n)、额外 O(1)(迭代)。
详细解释:
static class Node{int v;Node next;Node(int v){this.v=v;}}
static Node reverseK(Node head,int k){
Node p=head; for(int i=0;i<k;i++){if(p==null)return head;p=p.next;}
Node prev=null,cur=head; for(int i=0;i<k;i++){Node n=cur.next;cur.next=prev;prev=cur;cur=n;}
head.next=reverseK(cur,k); return prev;
}常见追问与易错点: k<=1 直接返回;尾部不足 k 个保持原序;递归深度约 n/k,极大链表应改迭代。
5. 反转双向链表
面试先答: 遍历每个节点交换 prev 与 next,最后返回原尾节点,时间 O(n)、空间 O(1)。
详细解释:
static class DNode{int v;DNode prev,next;}
static DNode reverse(DNode head){ DNode cur=head,newHead=null;
while(cur!=null){DNode n=cur.next;cur.next=cur.prev;cur.prev=n;newHead=cur;cur=n;} return newHead; }常见追问与易错点: 反转后新头的 prev 必须为空;同时维护链表的 tail 指针;空链表安全。
6. 用栈实现队列(线程安全)
面试先答: 两个栈:入栈 in,出队栈 out;out 为空时把 in 全部转移,均摊 O(1)。线程安全可用 ReentrantLock 保护状态,或直接使用并发队列而非手写。
详细解释:
final class TwoStackQueue<T>{
private final Deque<T> in=new ArrayDeque<>(),out=new ArrayDeque<>();
private final ReentrantLock lock=new ReentrantLock();
void offer(T x){lock.lock();try{in.push(x);}finally{lock.unlock();}}
T poll(){lock.lock();try{move();return out.poll();}finally{lock.unlock();}}
private void move(){if(out.isEmpty())while(!in.isEmpty())out.push(in.pop());}
}常见追问与易错点: ArrayDeque 不允许 null;只给单个方法加锁不足以保证复合操作;高并发场景可选 LinkedBlockingQueue。
7. LRU缓存设计
面试先答: HashMap 定位节点,双向链表维护新旧顺序,get/put 都是 O(1);容量超限淘汰尾节点。并发版本需锁或分段设计。
详细解释: Java 可直接继承 LinkedHashMap 并开启 accessOrder;生产实现要考虑过期、统计、并发和大对象。手写时节点包含 key/value/prev/next,get 后移到头部,put 更新或插入,淘汰尾部并从 map 删除。
class Lru<K,V> extends LinkedHashMap<K,V>{
private final int cap; Lru(int cap){super(cap+1,.75f,true);this.cap=cap;}
protected boolean removeEldestEntry(Map.Entry<K,V> e){return size()>cap;}
}常见追问与易错点: get 会修改访问顺序,因此读操作也可能需要写锁;容量为 0 的行为需定义;不要把 LinkedHashMap 当成高并发缓存。
8. 合并两个有序数组
面试先答: 若 nums1 尾部有足够空间,从后向前比较填充,避免覆盖未处理数据,时间 O(m+n)、额外 O(1)。
详细解释:
static void merge(int[] a,int m,int[] b,int n){int i=m-1,j=n-1,k=m+n-1;while(j>=0){if(i>=0&&a[i]>b[j])a[k--]=a[i--];else a[k--]=b[j--];}}常见追问与易错点: 必须继续处理 b 的剩余元素;a 剩余元素已在正确位置;数组容量应至少 m+n。
9. 升序数组找 target 的开始位置和结束位置
面试先答: 两次 lowerBound:第一次找 target,第二次找 target+1;结果为 [left,right-1],不存在返回 [-1,-1],时间 O(log n)。
详细解释: 对 Integer.MAX_VALUE 不要直接 target+1 溢出,可写 upperBound 使用 a[m] <= target 条件。边界包括空数组、全小于/全大于 target 和全部相等。
static int[] range(int[] a, int t) {
int l = lower(a, t), r = upper(a, t) - 1;
return l < a.length && l <= r ? new int[]{l, r} : new int[]{-1, -1};
}
static int lower(int[] a, int t) { int l=0,r=a.length; while(l<r){int m=l+(r-l)/2;if(a[m]<t)l=m+1;else r=m;}return l; }
static int upper(int[] a, int t) { int l=0,r=a.length; while(l<r){int m=l+(r-l)/2;if(a[m]<=t)l=m+1;else r=m;}return l; }常见追问与易错点: 明确闭区间/半开区间;不要线性扫描导致复杂度退化。
10. 数字1的个数(LC233)
面试先答: 按个位、十位等高低位分解,统计每一位上数字 1 的出现次数,时间 O(log n)、空间 O(1)。
详细解释: 对 factor=1,10,...,high=n/(factor*10)、cur=n/factor%10、low=n%factor:cur=0 加 highfactor;cur=1 加 highfactor+low+1;cur>1 加 (high+1)*factor。n=0 返回 0,使用 long 防乘法溢出。
static long countOne(int n) {
if (n <= 0) return 0; long ans=0;
for (long f=1; f<=n; f*=10) { long high=n/(f*10), cur=n/f%10, low=n%f;
ans += cur==0 ? high*f : cur==1 ? high*f+low+1 : (high+1)*f;
if (f > n/10) break;
} return ans;
}常见追问与易错点: 题目是统计 1..n,不是整数转字符串计数;n=0 和负数输入需约定(通常只接受非负)。
11. 糖果分发(DP)
面试先答: 每个孩子至少一颗,评分更高者比相邻低分者多。两趟 DP(左到右、右到左)取每位最大值,时间 O(n)、空间 O(n);也可用斜坡法降到 O(1)。
详细解释: left[i]=ratings[i]>ratings[i-1]?left[i-1]+1:1,right 同理,答案累加 max(left[i],right[i])。相等分数不要求更多糖果。空数组返回 0。
static long candy(int[] r) {
int n=r.length; if(n==0)return 0; int[] l=new int[n],right=new int[n];
Arrays.fill(l,1); Arrays.fill(right,1);
for(int i=1;i<n;i++) if(r[i]>r[i-1]) l[i]=l[i-1]+1;
for(int i=n-2;i>=0;i--) if(r[i]>r[i+1]) right[i]=right[i+1]+1;
long sum=0; for(int i=0;i<n;i++) sum+=Math.max(l[i],right[i]); return sum;
}常见追问与易错点: 只做一趟会漏掉反方向约束;结果可能超过 int 时使用 long。
12. 扁平数组化成树(二叉树)
面试先答: 先按 id 建节点 Map,再按 parentId 连接 children,最后找根;两遍 O(n),需要处理孤儿节点、重复 id 和环。
详细解释:
record Item(int id,Integer parentId,String name){}
static Map<Integer,List<Item>> toTree(List<Item> items){
Map<Integer,List<Item>> children=new LinkedHashMap<>(); Set<Integer> ids=new HashSet<>();
for(Item x:items){if(!ids.add(x.id()))throw new IllegalArgumentException("duplicate id");children.computeIfAbsent(x.id(),k->new ArrayList<>());}
List<Item> roots=new ArrayList<>();
for(Item x:items){if(x.parentId()==null)roots.add(x);else if(!ids.contains(x.parentId()))throw new IllegalArgumentException("orphan");else children.get(x.parentId()).add(x);}
return children; // roots 可由调用方另行返回
}常见追问与易错点: 真实返回值通常应包含 roots 和 children;大数据可流式构建;必须防止自环和祖先环导致无限遍历。
13. 二叉树的层次遍历及变体
面试先答: 使用队列逐层处理,当前层 size 固定后循环 size 次;时间 O(n)、空间 O(w)(w 为最大宽度)。之字形遍历可按层次反转或使用双端队列。
详细解释:
static List<List<Integer>> level(TreeNode root){
List<List<Integer>> ans=new ArrayList<>(); if(root==null)return ans; Queue<TreeNode> q=new ArrayDeque<>();q.add(root);
while(!q.isEmpty()){int sz=q.size();List<Integer> row=new ArrayList<>(sz);while(sz-->0){TreeNode x=q.remove();row.add(x.val);if(x.left!=null)q.add(x.left);if(x.right!=null)q.add(x.right);}ans.add(row);}return ans;
}
static class TreeNode{int val;TreeNode left,right;}常见追问与易错点: 空树返回空列表;按层求平均、最大值、右视图都可复用 size;不要在循环中动态使用变化后的 q.size()。
十四、场景设计题
统一答题框架:先明确需求与指标,再给架构和核心流程,说明一致性、性能与风险取舍。以下示例指标仅用于面试表达,需按真实业务调整。
1. 设计一个秒杀系统?
面试先答: 核心是削峰、限流、库存原子扣减和防重复下单。请求经 CDN/WAF、网关令牌桶、活动缓存后进入队列,异步创建订单;Redis 预扣库存,数据库最终校验,超时未支付回补。
详细解释: 需求:高峰 QPS、库存不超卖、用户一单、结果可查询。架构:静态化页面+分片 Redis+MQ+订单库分库分表。流程:资格校验→Lua 原子扣库存/设置用户标记→发送下单消息→消费者幂等落库。通过唯一索引和状态机保证最终一致;热点 key 分片、读写分离、批量消费降低 DB 压力。
常见追问与易错点: Redis 成功但 MQ 发送失败需 Outbox/重试;不能只依赖前端按钮防重复;库存回补必须幂等;超卖防线应在数据库唯一约束和条件更新。
2. 设计一个抢票系统?
面试先答: 票被唯一锁定并在支付超时释放,关键是座位/票号一致性和防并发占用。用 Redis 短锁或 Lua 预占,订单状态机控制支付,数据库唯一约束兜底。
详细解释: 需求含选座、临时锁定、支付、退票和查询。架构按车次/场次分片,座位库存按区段建模;预占写 Redis 并落 Reservation,MQ 异步通知订单。支付成功采用幂等回调更新出票;定时任务扫描超时订单释放锁。跨服务一致性用 Saga/本地消息表。
常见追问与易错点: 锁粒度过大影响并发;锁必须带 token 防误删;区间票不能简单按总库存扣减;订单和支付超时存在竞态,要用版本号/CAS。
3. 设计一个微信红包系统?
面试先答: 红包金额必须一次生成且总额守恒,抢红包高并发下要原子分配、幂等和异步记账。红包详情缓存,Lua/数据库事务扣减剩余金额,MQ 异步写账与通知。
详细解释: 发红包时校验余额并生成金额(随机或等额)和红包状态;抢红包用用户-红包唯一键防重复,原子取出一份金额,写领取记录和资金流水。资金相关操作以账务库为最终事实,缓存只做加速;失败消息重试,超过阈值进入人工对账。隐私和风控包括频控、设备指纹和金额上限。
常见追问与易错点: 不能仅依赖 Redis 作为资金账本;金额使用分为单位整数,避免浮点误差;重复消费必须幂等。
4. 设计一个延迟队列?
面试先答: 按延迟、可靠性和吞吐选择时间轮、Redis ZSet、MQ 延迟消息或数据库扫描。统一用任务状态、唯一 id 和重试策略保证至少一次,消费者幂等。
详细解释: Redis ZSet score 为执行时间,轮询 ZRANGEBYSCORE 后用 Lua 抢占并转入 ready 队列;时间轮适合大量短延迟、内存驻留;MQ 原生延迟消息适合持久化和分布式扩展。任务执行失败按指数退避,超过次数进入死信。时钟漂移和重启恢复需使用数据库/日志持久化。
常见追问与易错点: 取出与删除必须原子;轮询间隔决定精度与 CPU;“恰好一次”通常无法端到端保证,应设计幂等。
5. 设计一个分布式ID生成器?
面试先答: 常用 Snowflake(时间戳+机器号+序列号)或号段模式。要求唯一、趋势递增、可用性高;需处理时钟回拨、机器号分配和序列耗尽。
详细解释: Snowflake 单机每毫秒生成 2^sequenceBits-1 个 ID,机器号通过配置中心/注册表租约分配。时钟回拨小范围等待,大范围拒绝或切换逻辑时钟;号段模式从 DB 一次申请区间,应用内递增,DB 压力低但需处理号段浪费。ID 不应泄露业务规模时可加扰或改用 UUIDv7。
常见追问与易错点: “全局递增”与“全局唯一”不同;多活机房需纳入机房位;序列位越小并发越低。
6. 设计一个分布式锁?
面试先答: Redis 锁用 SET key value NX PX ttl,释放用校验 value 的 Lua;业务执行时间不确定时续期。强一致场景优先 ZooKeeper/数据库租约,并配合 fencing token 防止过期客户端继续写。
详细解释: 获取锁返回 token;看门狗续期但要有最大持有时间;异常释放依赖 TTL。Redlock 不能替代共识系统,网络分区下要明确安全性假设。真正保护资源时把递增 fencing token 传给存储层,拒绝旧 token 写入。锁业务必须幂等,避免把锁当事务。
常见追问与易错点: DEL 不能无条件执行;客户端暂停可能导致锁过期后继续操作;锁粒度与超时需要指标监控。
7. 设计一个接口限流系统?
面试先答: 令牌桶允许一定突发,漏桶平滑速率,滑动窗口精确但成本高。网关按用户、IP、接口和租户多维限流,Redis Lua 保证分布式原子计数,超限返回 429 并告知重试时间。
详细解释: 先在边缘层挡住恶意流量,再在服务层保护线程池和数据库。令牌桶维护 token 数和上次时间,Lua 一次计算补充和扣减。配置中心动态下发阈值,白名单和降级策略独立。限流指标包括通过率、拒绝率、延迟和热点 key。
常见追问与易错点: 单机本地限流在集群下不准确;时钟统一;Redis 故障时要有本地 fail-open/fail-close 策略。
8. 设计一个评论系统?
面试先答: 评论写入走审核和反垃圾,读侧按内容分片缓存并分页;楼中楼用 parent/root id 建树但限制深度。点赞、回复等异步化,数据库是事实源。
详细解释: 表含 comment、status、root_id、parent_id、path、作者和时间;热评榜可用 Redis Sorted Set,异步更新计数。发布流程:鉴权→敏感词/风控→落库→事件通知;删除采用软删除并保留审计。分页用 (create_time,id) 游标,避免深页 offset。多租户和隐私权限在查询层过滤。
常见追问与易错点: 计数缓存会丢失,需定期对账;树深度过大导致查询爆炸;编辑与审核状态要有版本控制。
9. 设计一个 feed 流系统?
面试先答: 关注关系和内容规模决定推模式。普通用户用写扩散(发布时推入粉丝收件箱),大 V 用读扩散(读取时合并),采用时间线游标分页和缓存。
详细解释: 关注表分片存储;发布事件进入 MQ,按粉丝量分层:小于阈值批量 fan-out,大 V 只写自己的 outbox。读取合并关注列表与大 V 流,按时间排序,去重后返回。缓存采用 Redis ZSet/流,设置 TTL;删除和隐私变更通过撤回事件异步传播。指标关注首屏 RT、漏读率和 fan-out 延迟。
常见追问与易错点: offset 分页会因新内容重复/漏读;取消关注需清理或过滤旧收件箱;热点用户不能同步给数百万粉丝推送。
10. 弱网环境下上传1G视频,应该怎么设计?
面试先答: 客户端分片、断点续传、并行上传、校验和重试;服务端使用对象存储 Multipart Upload,完成后异步转码。上传凭证短期有效,分片幂等。
详细解释: 先创建 uploadId,客户端计算文件指纹并查询已完成分片;每片 5–100MB,失败指数退避,限制并发避免拥塞。服务端校验分片 hash 和总大小,合并时校验 ETag/整体 hash。完成事件触发病毒扫描、转码和 CDN 刷新;未完成任务定期清理。网络切换时保留本地进度和校验信息。
常见追问与易错点: 不要让应用服务器中转全部大文件;并发过高会耗尽连接;恶意分片和重复上传要做配额与鉴权。
11. 100个进程,5个并发数的工作,应该怎么设计?
面试先答: 用固定大小为 5 的 worker 池消费 100 个任务,任务状态持久化并支持重试、超时和取消。若跨机器,用队列+5 个并发租约;若单机,用 ExecutorService。
详细解释: 提交任务获得 id,队列保存待执行状态;worker 抢占时使用租约/版本号,执行成功标记完成,失败按次数退避。并发数是全局还是单实例必须明确;全局 5 需要 Redis 信号量或调度中心。监控队列深度、运行数、平均耗时和失败率。
常见追问与易错点: 线程池大小不等于全局并发;任务必须幂等;进程崩溃后租约过期才能重新调度。
12. 高并发场景下,如何解决数据库读写性能瓶颈?
面试先答: 先定位瓶颈,再分层治理:缓存热点读、索引和 SQL 优化、连接池与批量写、读写分离/分库分表,最终用异步队列削峰。关键写路径仍以数据库约束保证正确性。
详细解释: 用慢查询、锁等待、QPS/RT 和 buffer 命中率定位;避免大事务和深分页。缓存采用旁路模式并处理击穿、穿透和一致性;读副本接受延迟并在关键读走主库。分片键要避免热点,迁移采用双写校验和灰度切换。容量规划需压测并留余量。
常见追问与易错点: 盲目加缓存会引入一致性问题;分库分表不能解决所有写热点;读写分离后要考虑读己之写。
13. 抛开MQ,自己实现延迟功能,你会怎么做?
面试先答: 小规模可用数据库时间索引+定时扫描;中规模用 Redis ZSet;高精度高吞吐用分层时间轮。任务取出要原子抢占,状态和重试记录持久化。
详细解释: 扫描器每 100–1000ms 查询到期任务,UPDATE ... WHERE status=WAITING 通过版本号抢占;执行线程池处理,失败写下次执行时间。Redis 方案用 Lua 从 zset 移到 ready list,重启时从 DB 重建。时间轮按 tick 分桶,长延迟分层转移到近层。
常见追问与易错点: 定时器漂移和集群重复扫描;不能在扫描事务里执行慢业务;至少一次投递要求幂等。
14. 设计一个RPC框架时,序列化模块最优先考虑的因素是什么?
面试先答: 首要是协议兼容与安全,其次才是性能和体积。需要明确 schema、版本演进、类型边界、跨语言能力,并限制反序列化类和输入大小。
详细解释: Protobuf/Thrift 有显式 schema 和向后兼容规则,JSON 可读但体积和性能较差,Java 原生序列化存在安全与版本问题不建议用于不可信输入。序列化层应支持超时、压缩协商、校验和和错误码;基准测试关注吞吐、P99、分配率和包大小。
常见追问与易错点: “最快”不等于“最合适”;字段编号复用会破坏兼容;反序列化失败要区分协议错和业务错。
15. 介绍docker代码沙箱的实现流程?
面试先答: 接收代码→编译镜像/复用缓存→创建受限容器→挂载只读输入→执行并采集 stdout/stderr/退出码→超时或资源超限强杀→清理。调度层限制并发并记录审计。
详细解释: 镜像按语言和版本预构建,运行时禁网、非 root、只读根文件系统、临时目录限额;使用 cgroup 设置 CPU/内存/PID,seccomp/AppArmor 限制系统调用。宿主机与容器之间只通过受控目录或 stdin/stdout 交换。结果返回统一状态:编译错、运行错、超时、资源超限。
常见追问与易错点: Docker 隔离不是绝对安全,生产需 gVisor/Firecracker 等更强隔离;不要把宿主机 socket 暴露给容器;日志必须限长。
16. 代码沙箱如何防止恶意代码攻击(如死循环、占满内存、文件读写)?
面试先答: 通过多层防护:超时 watchdog、cgroup CPU/内存/PID 限制、禁网、最小权限、只读文件系统、seccomp 白名单和输出截断;宿主机与控制面隔离,异常容器立即销毁。
详细解释: --memory、--cpus、--pids-limit 控制资源;timeout 只作补充,需监控容器状态并强制 kill。挂载临时目录并限制容量,禁止设备和特权模式。镜像定期扫描,运行账号无 root,主机内核及时更新。所有执行请求鉴权、限额、审计。
常见追问与易错点: 仅禁用 Java API 不可靠,恶意代码可反射或调用 native;OOM 可能拖垮宿主机,必须 cgroup+节点级隔离;清理失败要有后台回收器。
17. Docker沙箱在高并发判题场景下,如何避免资源争抢与容器反复创建?
面试先答: 预热固定镜像和容器池,调度器按资源令牌分配;每次执行使用独立工作目录和 namespace,任务完成后重置或销毁。节点设置并发上限、队列和公平调度。
详细解释: 镜像分层缓存减少拉取,长驻 sandbox 通过清空进程、文件和网络状态复用,但高风险语言或异常后必须销毁重建。按 CPU/内存估算 admission control,避免过度承诺;节点满载时排队或降级。监控容器启动耗时、资源利用率、失败率和僵尸容器。
常见追问与易错点: 复用容器不能泄漏上一个用户数据;池大小需压测;容器池本身不是安全边界,需定期轮换。
18. 如何设计一个日志系统?如何处理海量日志?
面试先答: 应用结构化输出 JSON(traceId、时间、级别、服务、业务键),Agent 批量采集到 Kafka,再由 Flink/Logstash 清洗后写 Elasticsearch/对象存储;热数据检索、冷数据归档。采集端背压和丢弃策略要明确。
详细解释: 业务日志与审计日志分流,敏感字段脱敏。Kafka 分区按服务/时间,消费者批量写入并设置索引生命周期;ES 只保留近期数据,历史转 Parquet/对象存储。查询限制时间范围和返回条数,避免深分页。监控采集延迟、堆积、写入失败和成本;日志不可用时不能阻塞主业务。
常见追问与易错点: 不记录密码和 token;同步写远程日志会放大故障;traceId 要跨线程和 MQ 传递;日志级别需动态调整并限频。
十五、AI/大模型/Agent
版本说明:Function Calling、MCP、模型上下文窗口和 SDK 参数会随供应商版本变化;回答先讲稳定原理,再指出需以当前官方协议为准。
15.1 Agent基础
1. 什么是Agent?组成部分有哪些?
面试先答: Agent 是以大模型为推理核心,能感知上下文、规划步骤、调用工具并根据结果循环执行的系统。典型组成是模型、提示词/策略、状态记忆、工具适配器、执行器、权限与观测模块。
详细解释: 一次任务通常经过意图理解→计划→工具选择→执行→观察→纠错/继续→输出。编排器维护最大步数、预算、超时和终止条件。记忆分短期会话上下文和长期用户事实;工具层定义 schema、鉴权、超时、重试与幂等。
常见追问与易错点: Agent 不等于“多轮聊天”;自主性越高风险越大,写操作必须审批和沙箱化;应能回放每一步轨迹。
2. Agent和大模型怎么交互、协同?工作流程是什么?
面试先答: Agent 把任务、上下文和可用工具 schema 发给模型;模型返回自然语言或结构化 tool call;执行器调用工具并把结果作为新消息回传,循环直到模型返回最终答案或触发停止条件。
详细解释: 角色消息通常含 system、user、assistant、tool。编排器校验模型输出的工具名和参数,工具结果带状态、耗时和错误码。每轮都检查 token 预算、权限和副作用;达到最大步数、用户取消或模型标记完成则结束。流式输出要区分“展示文本”和“未执行完的调用”。
常见追问与易错点: 工具结果不能未经转义拼接进 system prompt;模型可能重复调用,需幂等键和循环检测;模型不是事务协调器。
3. 为什么要给大模型引用工具?单纯大模型不行吗?
面试先答: 模型擅长语言推理但知识可能过时、无法访问私有数据,也不应直接执行副作用。工具提供实时检索、精确计算、业务操作和可验证结果,降低幻觉。
详细解释: 天气、库存、订单状态等必须查实时系统;数学和 SQL 执行交给专用引擎;发邮件、扣款等操作要经过权限和审批。工具调用带来延迟、失败和安全面,因此需最小权限、参数校验、超时和审计。
常见追问与易错点: 工具不是越多越好,schema 过多会占上下文并增加误选;只读工具和写工具要分级。
4. 工具给大模型扩展了什么能力?
面试先答: 扩展四类能力:获取外部/实时信息、计算和代码执行、操作业务系统、长期记忆与文件处理。工具把不可验证的生成转为可观测的动作。
详细解释: 搜索和 RAG 提供知识,数据库/计算器提供确定性结果,浏览器/支付/工单 API 执行业务,沙箱执行代码。每个工具应描述输入输出、错误、权限和副作用,避免让模型猜接口。
常见追问与易错点: 工具结果仍可能被污染或提示注入;必须限制返回大小和敏感字段。
5. 什么是Function Call?
面试先答: Function Call 是模型按照开发者提供的函数名称和 JSON Schema 生成结构化调用参数,应用执行函数后把结果回传模型。模型只提出调用,不应被视为已执行。
详细解释: 流程为 tools 定义→模型选择工具→服务端校验参数→执行→tool message 回传。schema 要限制枚举、范围和必填字段;服务端必须再次鉴权,不能信任模型传来的用户 id。不同供应商字段名和并行调用支持不同,应由适配层屏蔽。
常见追问与易错点: JSON 解析失败要修复或重试;不要把函数描述中的秘密放入 prompt;副作用调用需要幂等和人工确认。
6. 什么是MCP?MCP的组成是什么?
面试先答: MCP(Model Context Protocol)是让模型客户端以统一协议发现和调用外部能力的开放协议。核心角色通常是 host/client 和 server,server 暴露 tools、resources、prompts 等能力,具体传输和版本以当前规范为准。
详细解释: 客户端建立会话并发现能力,调用时传入结构化参数,服务端返回内容或错误。MCP 的价值是工具可插拔、跨应用复用,而不是替代业务鉴权和沙箱。生产实现要做来源信任、权限隔离、超时、审计和版本协商。
常见追问与易错点: MCP 是协议,不是某个具体工具库;不能因“标准化”就跳过安全校验;不同 SDK 的能力集合可能不同。
7. 什么是Skill?和Function Call、MCP的区别是什么?
面试先答: Skill 通常是面向任务的可复用指令、流程、资源和工具组合;Function Call 是一次结构化函数调用;MCP 是连接模型客户端与工具/资源服务器的协议。Skill 偏工作流和知识封装,Function Call 偏单次动作,MCP 偏互操作。
详细解释: 一个“发布版本”Skill 可包含检查清单、脚本、审批步骤,并在过程中调用多个函数或 MCP 工具。Skill 的上下文可按需加载,避免把全部细节塞进 system prompt。具体 Skill 定义取决于产品生态,应明确版本和权限边界。
常见追问与易错点: 不要把 Skill 当成协议;Skill 也必须进行输入校验和审计;名称相近不代表实现兼容。
8. MCP和Skill哪个上下文占用大?
面试先答: 没有固定答案。MCP 连接通常需要工具 schema 和资源描述;Skill 可能包含长指令、示例和脚本说明。按需发现、摘要和分层加载后,实际 token 取决于具体定义。
详细解释: 优化方式包括只暴露当前任务相关工具、压缩 schema 描述、延迟读取资源、对 Skill 使用索引和摘要。监控每轮输入 token、缓存命中和工具选择准确率,以数据决定方案。
常见追问与易错点: 不要简单断言“Skill 一定更大/更小”;上下文窗口和计费规则随模型版本变化。
9. Agent怎么判断某轮tool call后该不该结束?
面试先答: 由模型意图、任务验收条件和编排器硬约束共同决定。模型返回最终消息且必需事实已获得时结束;仍缺信息、工具失败可恢复或验收未通过时继续;达到步数、预算、超时或风险阈值时强制停止并解释。
详细解释: 为每类任务定义 done predicate,例如订单查询必须拿到订单状态和权限校验,代码任务必须编译/测试通过。循环检测相同 tool+参数,连续失败触发降级。高风险写操作要求用户确认后才结束。
常见追问与易错点: 不能只看模型说“完成”;工具成功不代表业务目标完成;停止策略要可测试。
10. Agent生成代码执行用什么方法?了解过sandbox吗?
面试先答: 代码在隔离沙箱中执行:临时工作区、非 root、禁网、只读镜像、cgroup 资源限制和超时;采集编译/运行结果后再由 Agent 决定修复或输出。高风险代码使用微 VM 或远程执行节点。
详细解释: 执行请求带语言版本、依赖白名单、CPU/内存/磁盘上限和最大输出。每次运行有 trace id,可回放命令和日志;沙箱不挂载宿主机 socket,不访问生产凭证。结果分编译错、测试失败、超时和资源超限。
常见追问与易错点: 进程级限制不足以隔离内核攻击;依赖下载会引入供应链风险;不要让模型决定放宽安全策略。
11. Agent执行代码工具如何设计?报错了怎么办?
面试先答: 工具接口应显式声明代码、语言、入口、资源限制,返回结构化状态、stdout/stderr、诊断位置和 trace id。可恢复错误交给 Agent 修复重试,不可恢复错误直接停止并给用户可读原因。
详细解释: 参数校验后入队执行,超时由 watchdog 终止;输出限长并保留对象存储链接。编译错误提供行列号,运行错误区分用户代码与沙箱基础设施。重试使用新工作目录和幂等执行 id,避免残留状态。
常见追问与易错点: 不要只返回一段字符串;错误信息需脱敏;重试次数和成本要有上限。
12. 重试做在tool内还是agent多次调用?
面试先答: 瞬时基础设施错误的有限重试放在 tool 内,保持接口语义稳定;需要改变参数、策略或换工具的重试由 Agent 决定。两层要避免叠加造成指数放大。
详细解释: tool 内按错误码区分连接超时、429、业务拒绝;使用指数退避和抖动。返回 retryable、attempts 和建议等待时间,Agent 据此调整。副作用操作需幂等键,未知错误宁可人工介入。
常见追问与易错点: 不应重试参数校验错误;总预算和 deadline 贯穿两层;重试结果必须可观测。
13. 如果一轮tool call返回结果非常多,怎么设计?
面试先答: 工具分页、过滤和聚合,默认只返回摘要;完整结果存对象存储并返回引用。Agent 可按需继续查询,设置 token、行数和字节上限。
详细解释: 结果 schema 包含 summary、items、next_cursor、truncated。对日志/搜索先做 top-k、字段投影和去重;大文件提供下载或分块读取工具。敏感数据在工具层脱敏,避免通过摘要泄露。
常见追问与易错点: 截断必须显式告知;不要把分页 token 交给不可信用户;上下文压缩不能丢失关键约束。
14. Coding agent会有哪些模块?
面试先答: 常见模块包括需求解析、代码库索引、计划器、编辑器、构建/测试执行器、工具与权限层、上下文记忆、评审器和可观测系统。模块通过事件和 trace id 连接。
详细解释: 代码索引提供符号、依赖和语义检索;计划器拆解任务并维护验收条件;编辑器生成可审阅 diff;执行器在沙箱运行测试;评审器检查编译、测试、安全和风格。大项目需增量索引、变更范围控制和人工确认。
常见追问与易错点: 不能让 Agent 直接覆盖整个仓库;每次修改都应可回滚;测试通过不代表需求正确。
15. 多个agents怎么相互协作?
面试先答: 采用编排器分解任务,按依赖并行派发角色 Agent,通过共享工件和消息汇报结果,最后由集成 Agent 合并和验收。共享写资源需要锁或分支隔离。
详细解释: 例如分析、实现、测试、审查四类 Agent;任务含输入、输出 schema、deadline 和权限。代码修改在独立 worktree,合并前运行冲突检查和全量测试。失败可重派或降级为人工处理,避免 Agent 无限互相调用。
常见追问与易错点: 多 Agent 不必然更好,会增加 token 和协调成本;明确单一事实源、负责人和冲突解决策略。
16. Agent间通信和状态管理怎么设计?
面试先答: 消息采用版本化 schema,状态存持久化状态机,工件存对象存储/数据库,事件带 traceId、幂等键和因果关系。短期上下文与长期记忆分离,敏感字段加密和最小权限。
详细解释: 状态包括任务、步骤、工具调用、重试和审批;采用 append-only 事件或乐观锁更新,支持断点恢复和回放。消息总线提供至少一次投递,消费者幂等。跨 Agent 共享上下文用引用而不是复制全文,减少 token。
常见追问与易错点: 不能只靠内存变量;状态迁移需兼容旧版本;并发更新要检测版本冲突。
17. Agent效果评估怎么做?
面试先答: 同时评估任务成功率、答案正确性、工具选择/参数准确率、步骤数、延迟、成本和安全事件。离线用固定基准集,在线做抽样人工评审和回归监控。
详细解释: RAG/工具任务可定义可验证指标(SQL 结果、测试通过、引用正确);开放问答用 rubric 评分和成对偏好。记录完整轨迹,按模型、prompt、工具版本分桶。上线前做对抗测试、提示注入和权限越权测试,变更采用灰度。
常见追问与易错点: 只看 BLEU/相似度不代表业务成功;成本和安全是硬指标;评测集要防止泄露到训练/提示词。
15.2 RAG
1. RAG和传统搜索的区别是什么?
面试先答: 传统搜索直接返回文档,RAG 在检索后把相关片段注入上下文,让模型生成带依据的答案。RAG 更适合综合问答,但多了切片、召回、上下文和幻觉治理成本。
详细解释: 搜索强调相关性排序和点击;RAG 还要做 query 改写、重排、引用和答案验证。两者可组合:关键词/BM25 负责精确召回,向量负责语义召回,模型负责综合表达。
常见追问与易错点: RAG 不会自动保证正确;检索不到时应明确说不知道而非编造。
2. 为什么不直接用关键词检索?
面试先答: 关键词对专有名词、编号和精确过滤很强,但无法覆盖同义表达、错别字和自然语言意图。向量检索补充语义匹配,混合检索通常效果更稳。
详细解释: 例如“报销多久到账”和“费用审批时效”词面不同但语义相近;相反版本号和错误码需要 BM25 精确匹配。通过召回融合和 reranker 解决单一路径偏差。
常见追问与易错点: 向量检索也可能语义过泛;不要盲目放弃倒排索引。
3. RAG混合检索策略怎么做?
面试先答: 并行执行 BM25/ES 和向量 ANN,分别取 top-k,用加权分数或 RRF 融合,再用 cross-encoder 重排,最后按 token 预算截取并去重。
详细解释: 先统一文档权限和过滤条件,避免召回后才泄露;权重通过离线标注集调参。对长文档保留标题、来源和时间元数据,重排后可按多样性(MMR)减少重复。
常见追问与易错点: 分数不可直接相加时用 RRF;召回 k 太小会导致上限受限,太大增加延迟和噪声。
4. RAG召回效果如何评估?
面试先答: 用标注问题-相关片段集评估 Recall@k、Precision@k、MRR/NDCG;端到端再评估答案正确率、引用准确率和无依据生成率。
详细解释: 区分“召回到了正确片段”和“模型使用正确片段”。按问题类型、文档版本、权限和长尾查询分桶,记录 p95 延迟。线上用点击、追问率和人工抽检做反馈,但不能把点击简单等同于正确。
常见追问与易错点: 没有标注集就无法客观调参;评估集需覆盖同义问、否定问和无答案问。
5. Chunk切片粒度太大/太小有什么问题?
面试先答: 太大导致噪声多、超上下文和定位不准;太小丢失语义和上下文,召回片段无法独立回答。通常按标题/段落语义切分,并设置重叠窗口。
详细解释: 代码按类/方法切,表格按行组切,FAQ 一问一答切。为每块保存父文档、章节、版本和权限元数据;检索命中子块时可扩展邻近块。粒度通过 Recall、答案引用和 token 成本联合评估。
常见追问与易错点: 固定字符数不是万能;重叠过大导致重复和索引膨胀;中文按 token/标点更合理。
6. RAG系统文档频繁更新,向量索引与原文不一致怎么办?
面试先答: 文档采用版本号和内容 hash,更新事件触发增量切片/嵌入;索引写入临时版本后原子切换,检索结果带版本并校验。失败重试,旧版本按策略保留或删除。
详细解释: 采用 outbox 保证“原文变更事件”不丢;异步索引状态可观测。查询时按生效时间和权限过滤,关键答案可回源原文校验。双写期间用版本优先和灰度,避免混合旧新片段。
常见追问与易错点: 只更新向量不更新元数据会造成权限漏洞;删除必须有 tombstone,不能依赖自然过期。
7. 混合检索时,如果关键词匹配但语义不匹配怎么办?
面试先答: 先使用字段/类型过滤和重排模型判断上下文相关性;关键词命中低相关时降权,保留语义候选。对错误码、产品型号等强精确字段设置规则优先级。
详细解释: 融合阶段增加 query 类型识别;实体/数字匹配可设硬约束,通用问句用语义权重。通过标注难例调权,记录“关键词命中但被拒绝”的样本迭代。
常见追问与易错点: 不要完全依赖模型重排,延迟和成本会升高;过滤条件必须在召回前执行。
8. RAG效果优化有哪些方法?
面试先答: 优化查询改写、切片和元数据,采用混合召回+重排,控制上下文顺序与长度,增加引用/拒答校验,并用评测集闭环调参。
详细解释: 领域词典和同义词提升召回;多查询生成覆盖不同表达;Parent-Child 检索兼顾粒度;MMR 降低重复;答案要求引用片段并做 entailment 检查。缓存热点 query,异步预计算嵌入降低成本。
常见追问与易错点: 只增大 top-k 会增加噪声;优化需同时看质量、延迟和成本。
9. 什么是CoT(思维链)?
面试先答: CoT 是引导模型分步推理的提示方法,可提升复杂推理准确率;生产系统不一定展示完整内部推理,而是输出简要依据、步骤摘要或可验证中间结果。
详细解释: 可用少样本示例、结构化 scratchpad 或工具执行替代纯文本推理。思维链可能泄露敏感信息、增加 token 和延迟,因此需按任务使用并做输出过滤。模型是否支持、效果与版本有关。
常见追问与易错点: 详细解释不等于真实思维过程;不能把 CoT 当成正确性证明,仍需测试和验证。
10. RAG如何解决大模型幻觉?怎么体现/评估?
面试先答: 通过高质量检索、引用约束、无答案拒答、事实校验和工具验证降低幻觉,但不能完全消除。评估看 groundedness、引用准确率、答案正确率和拒答准确率。
详细解释: prompt 明确“仅依据证据回答”;检索为空或证据冲突时要求澄清。生成后用 NLI/规则/二次模型检查每个断言是否有来源。线上抽检事实错误、用户纠正率和高风险领域漏答率。
常见追问与易错点: 引用存在不代表引用支持结论;高风险医疗/财务应设置人工审核。
15.3 大模型与提示词工程
1. 常用哪些大模型?模型选型的考虑因素有哪些?
面试先答: 选型看任务质量、上下文长度、工具/结构化输出能力、延迟、成本、部署和数据合规。常见路线是通用大模型负责复杂推理,小模型处理分类/抽取,必要时使用私有化模型。
详细解释: 用代表性评测集比较准确率、幻觉、长上下文和中文/代码能力;压测 p95、并发、限流和 token 成本。涉敏数据优先选择合规部署或脱敏代理。模型、提示词和供应商通过适配层解耦,便于灰度切换。
常见追问与易错点: 不要只比较公开榜单;版本升级可能改变输出格式,必须回归测试。
2. 大模型API调用原理是什么?流式输出怎么实现?
面试先答: 客户端通过 HTTPS 鉴权提交消息和生成参数,服务端排队、推理并返回 token。流式通常使用 SSE/HTTP chunked,客户端逐片解析并在完成事件后关闭连接。
详细解释: 设置 connect/read timeout、重试和 request id;流式响应要处理断线、半包、心跳和客户端取消。服务端可通过网关做限流、敏感信息过滤和成本统计。不要在重试时重复执行带副作用的工具调用。
常见追问与易错点: 流式不是更快完成推理,只是更早展示首 token;必须处理 usage 可能在末尾才返回。
3. 写提示词的方法论是什么?对提示词工程的理解?
面试先答: 明确角色和目标,提供必要上下文、输入输出格式、约束、示例和失败处理;把可验证规则写成结构化 schema,并用评测集迭代,而不是依赖“更有礼貌”的措辞。
详细解释: 先定义任务成功指标,再设计最小 prompt;使用分隔符隔离用户内容,要求引用或 JSON 输出,列出边界和拒答条件。版本化管理 prompt,记录模型、温度、工具和结果,做 A/B 与回归。
常见追问与易错点: prompt 不是安全边界;密钥、权限和业务规则必须在代码/服务端强制执行。
4. 上下文工程的理解?和提示词工程的区别?
面试先答: 提示词工程关注指令如何表达;上下文工程关注在有限窗口内选择、组织、压缩和更新对当前任务有用的信息,包括历史、检索、工具结果和记忆。
详细解释: 上下文管线可做摘要、去重、时间衰减、相关性排序和权限过滤;长任务用外部状态保存中间结果,只把引用放入 prompt。两者需协同:好的指令无法弥补错误或过量上下文。
常见追问与易错点: 压缩不能丢失约束和来源;不同用户上下文必须逻辑隔离。
5. 如何避免不同用户上下文互相污染?
面试先答: 所有会话和记忆按 tenant/user/session 隔离,检索必须带权限过滤;服务端构建上下文白名单,禁止客户端直接指定 memory id。敏感数据加密、脱敏并设 TTL。
详细解释: 缓存 key 包含租户和模型版本,异步任务传递完整身份上下文;长期记忆写入前分类和用户授权,读取时按用途最小化。测试使用交叉用户并发和越权用例,日志不记录完整 prompt。
常见追问与易错点: 仅在 prompt 中写“不要泄露”不可靠;向量库 metadata filter 是必要而非可选。
6. 保存/隔离/选择/压缩上下文的方法有哪些?
面试先答: 保存到会话库和长期记忆库,按租户隔离;选择按相关性、时间和任务阶段;压缩用摘要、去重和分层存储,关键事实保留来源和置信度。
详细解释: 短期上下文保留最近轮次,旧内容滚动摘要;工具结果存引用;向量检索只取 top-k 并 rerank。达到 token 预算前预留输出和工具调用空间,超限时按优先级淘汰。
常见追问与易错点: 摘要错误会持续污染后续;支持用户查看、修改和删除长期记忆。
7. 如何持久化上下文?
面试先答: 结构化存储会话元数据、消息和工具轨迹,事件追加写入,定期生成摘要快照;大文档和附件放对象存储,索引存数据库/向量库。支持版本、过期和删除。
详细解释: 事务写消息与状态变更,异步生成 embedding;读时按 session、tenant 和权限分页加载。加密敏感字段,备份和灾备遵循数据保留策略。迁移时保留 schema version,兼容旧消息。
常见追问与易错点: 不要把整段 prompt 作为不可检索大字段;删除请求必须覆盖缓存、索引和备份生命周期。
8. 多轮prompt中当前prompt怎么利用之前的prompt?
面试先答: 客户端提交会话 id,服务端读取历史后按策略拼装当前上下文;不是简单无限拼接,而是保留系统约束、最近对话、相关记忆和本轮输入,必要时摘要。
详细解释: 每轮记录用户意图和工具结果;对历史做相关性检索,避免把无关闲聊带入。系统 prompt 由可信服务端固定,用户消息使用分隔符。窗口不足时先压缩低优先级内容。
常见追问与易错点: 历史用户消息不应覆盖 system 指令;多标签会话需明确继承范围。
9. Prompt管理:存什么?怎么存?相似度高怎么处理?
面试先答: 存模板、版本、变量 schema、适用模型、评测指标、作者、审批和变更 diff,不存密钥和未经脱敏的用户数据。相似模板通过规范化、embedding 聚类和人工合并去重。
详细解释: 模板仓库支持草稿→评审→发布→回滚,运行时按租户/场景灰度。记录实际渲染后的摘要和版本 id 便于追溯。相似度高但业务边界不同的模板保留标签和测试集,不能只按文本相似删除。
常见追问与易错点: 版本号必须和模型/工具版本绑定;提示词变更应触发回归评测。
15.4 向量数据库与检索
1. 向量数据库如何选型?
面试先答: 按数据规模、维度、过滤能力、索引类型、写入更新、延迟、持久化、运维和生态选型。小规模可用 PostgreSQL pgvector,搜索/过滤一体可选 ES,超大规模考虑 Milvus/专用云服务。
详细解释: 比较 HNSW、IVF、DiskANN 的召回-延迟-内存取舍;确认 metadata filter 是否先过滤、是否支持分片和备份。用真实数据压测 Recall@k、p95、写入吞吐和成本,不能只看厂商 benchmark。
常见追问与易错点: 向量库不是权限系统;embedding 模型升级需重建或双索引。
2. 检索召回速度怎么优化?
面试先答: 控制维度和 top-k,选择合适 ANN 参数,分片并行,预过滤元数据,缓存热点查询,批量 embedding,必要时量化或分层索引。
详细解释: HNSW 调小 efSearch 降延迟但损失召回;分区按租户/时间减少搜索范围;结果缓存需包含权限和过滤条件。用 p50/p95 以及召回率联合调参,避免只追求速度。
常见追问与易错点: 过滤过晚会把无权文档带入候选;缓存失效要与文档版本绑定。
3. ES千万级数据取top100,内部怎么执行?
面试先答: 查询先在每个 shard 本地过滤、打分并取 top 100(或更大候选),coordinating node 合并各 shard 的 top-N,再全局排序返回。深分页会增加 from+size 成本,应使用 search_after/PIT。
详细解释: 倒排索引先定位候选,doc values 用于排序/聚合;分片返回优先队列,协调节点归并。pre_filter_shard_size、查询缓存和合理 routing 可减少无关分片。top-k 与 rescore 需权衡网络和内存。
常见追问与易错点: 分片越多不一定越快;from=100000 会放大内存;排序字段需可 doc_values。
4. 检索这块做过效果上的优化吗?有什么优化案例?
面试先答(虚构示例): 在校园知识库中,把固定 500 字切片改为标题感知的 Parent-Child,加入 BM25+向量 RRF 和 cross-encoder 重排,Recall@5 从 0.71 提升到 0.86,p95 增加 35ms;通过缓存和批量嵌入控制成本。
详细解释: 先建立标注集按 FAQ、制度、代码三类分桶;定位错误来自切片、召回还是重排,再逐项改动。版本化索引并灰度,监控无答案拒答率、引用正确率和用户追问率。
常见追问与易错点: 指标和数据集必须可复现;不要只报提升百分比而不说明延迟、成本和统计显著性。
15.5 工具调用
1. 工具调用失败怎么办?
面试先答: 先按错误码判断是否可重试;瞬时错误有限重试并退避,参数/权限错误要求模型修正或向用户澄清,持续失败切换保底工具或人工处理。所有调用带 trace id 和幂等键。
详细解释: 返回结构化 errorType、retryable、message、requestId,不把堆栈和凭证暴露给模型。写操作在未知结果时先查询状态再决定是否重试。达到 deadline 后给出明确降级说明。
常见追问与易错点: 不能无限重试;超时不代表服务端未执行;副作用必须可查询和幂等。
2. 怎么溯源工具调用失败?是意图识别错、入参错还是工具掉线?
面试先答: 记录端到端 trace:用户意图、模型版本和原始 tool call、schema 校验、网关、服务端日志、依赖和响应。用错误分类和回放判定责任层。
详细解释: 先看模型是否选对工具,再看参数校验与权限,随后检查网络/服务健康和业务返回。脱敏后保留输入摘要和版本,支持按 requestId 重放。指标按错误类型分桶,避免只看总失败率。
常见追问与易错点: 日志不能记录 token/密码;链路时钟要统一;工具结果为空和工具异常是不同类别。
3. Agent系统可观测平台有哪些?langsmith和langfuse的区别?
面试先答: 可观测平台需覆盖 trace、prompt/模型版本、token/成本、工具调用、延迟、错误和评测。LangSmith 偏 LangChain 生态的一体化调试与评测;Langfuse 更强调开源、自托管、通用 tracing 和成本分析,具体功能随版本变化。
详细解释: 选型看数据合规、部署、SDK 覆盖、采样和告警集成。也可自建 OpenTelemetry+ClickHouse/ES,关键是统一 trace schema 和脱敏策略。
常见追问与易错点: 平台不是业务日志替代品;不要把用户敏感上下文原样发送第三方。
4. 工具重试不成功怎么办?保底工具策略?
面试先答: 达到重试上限后按优先级切换只读缓存、备用服务或人工队列;若无可靠保底,明确告知暂不可用,不编造结果。记录失败任务供异步补偿。
详细解释: 为工具声明 capability、数据新鲜度和一致性等级,Agent 根据任务风险选择。支付/删除等操作不能用过期缓存冒充成功;查询可返回最近快照并标注时间。保底切换需要熔断和恢复探测。
常见追问与易错点: 备用工具可能语义不同,需适配和验收;降级结果必须带来源和时间。
5. 入参校验怎么做?有什么现成手段?
面试先答: 采用 JSON Schema/Java Bean Validation 做类型、必填、范围和枚举校验,服务端再做权限、业务状态和资源归属校验。校验失败返回可修正字段和错误码。
详细解释: Function Call 的 schema 只是一层提示,不能替代后端校验。Java 可用 Jakarta Validation、Jackson 严格反序列化和自定义 validator;OpenAPI/JSON Schema 可生成客户端和测试。字符串长度、URL、SQL 等需白名单和编码处理。
常见追问与易错点: 不要直接拼接 SQL/命令;错误消息不能泄露内部字段;校验与业务操作之间仍可能有竞态。
15.6 AI编程工具
1. 日常使用哪些AI编程工具?
面试先答(虚构示例): 日常用 IDE 内联补全处理样板代码,用对话 Agent 做代码库搜索、测试生成和重构,用命令行模型做日志分析;涉及生产修改时只接受可审阅 diff,不直接自动合并。
详细解释: 工具选择看任务粒度、上下文能力、隐私和可控性。代码补全追求低延迟,Agent 适合多文件任务,静态分析工具负责确定性检查。所有生成内容都经过编译、测试、审查和许可证检查。
常见追问与易错点: 不要把工具熟练度等同于工程能力;公司代码是否可上传必须遵守政策。
2. 不同场景怎么切换使用AI编程工具?
面试先答: 小函数用补全,陌生模块用问答+索引,跨文件改动用计划型 Agent,线上故障用只读日志分析;安全/核心交易代码优先人工和确定性工具。
详细解释: 先定义输入、验收和回滚点,再选择工具;大任务拆成可独立验证的小步。工具上下文只提供必要文件,避免全仓库泄露和噪声。每一步生成 patch 并运行相关测试。
常见追问与易错点: 不要让 Agent 在没有测试的模块里大范围重写;模型不清楚时应先提问而非猜测。
3. AI Coding的流程是什么?大项目需求怎么解决?
面试先答: 需求澄清→代码库勘探→拆解计划→小步修改→自动测试/静态检查→人工审查→灰度发布。大项目按领域和依赖分阶段,每阶段有可验证产物。
详细解释: 先让工具生成架构摘要和影响面,确认后再编辑;使用 worktree/分支隔离并限制修改目录。测试从单元到集成、契约和性能,失败日志回传后定向修复。最终由负责人审查接口、数据迁移和安全。
常见追问与易错点: 不能用一次大 prompt 代替设计;迁移脚本和回滚方案必须人工确认。
4. 怎么保证AI生成代码的正确性和质量?
面试先答: 通过明确规范和验收、类型检查、单元/集成测试、静态扫描、依赖漏洞检查、代码评审和灰度监控;高风险功能要求人工双签。
详细解释: 让模型先生成测试和边界用例,再实现;使用编译器和 linter 作为硬门槛。对生成代码检查并发、资源释放、异常、日志脱敏和许可证。记录模型/提示词版本,发现回归可定位。
常见追问与易错点: 测试通过不代表需求正确;不能盲信模型解释;生成依赖要锁版本并扫描供应链。
5. Cursor从设计稿直接生成代码的挑战是什么?
面试先答: 难点在视觉意图转为可维护组件:布局约束、响应式、交互状态、设计系统和资源路径可能被误解。应先抽取 tokens/组件,再分区实现并用截图对比和可访问性测试验收。
详细解释: 设计稿缺少 hover、loading、错误和移动端状态;生成代码可能重复 CSS、硬编码尺寸或引入不匹配依赖。提供现有组件库、页面约束和目标浏览器,分步生成结构、样式和交互,人工校准。
常见追问与易错点: 像素相似不等于可用;必须测试键盘、语义标签、网络失败和不同屏幕。
十六、项目经验相关
以下回答均为虚构 Java 校招候选人“李明”在校园二手交易平台项目中的示例,不能当作用户真实经历。
1. 介绍一下你的项目架构?
面试先答: 虚构项目采用 Spring Boot 模块化单体,API、交易、搜索和通知按领域拆分;MySQL 为事实库,Redis 做缓存与分布式锁,Kafka 解耦异步任务,Elasticsearch 提供商品搜索,Docker 部署。
详细解释: 网关负责鉴权和限流,服务层通过事务维护订单和库存,Outbox 事件同步 ES/通知。读多写少查询走缓存和 ES,核心写请求仍落 MySQL。模块边界通过接口和事件通信,便于后续拆微服务。
常见追问与易错点: 需说明自己负责的范围和真实指标;不要把“用了很多组件”当架构亮点。
2. 项目中最有挑战/出彩的地方是什么?
面试先答(虚构示例): 挑战是开学促销时商品库存热点和搜索同步延迟。我用 Redis Lua 预扣+数据库条件更新防超卖,Outbox+Kafka 保证搜索最终一致,压测后 P99 从 800ms 降到 180ms。
详细解释: 先用监控定位热点 key 和慢 SQL,再逐步引入缓存、异步化和索引优化;通过唯一约束和对账任务验证正确性。灰度期间同时观察错误率、库存差异和消费堆积。
常见追问与易错点: 指标要能解释测量方法;性能提升不能牺牲数据正确性。
3. 项目中遇到最难的问题是什么?怎么解决的?
面试先答(虚构示例): 一次 Kafka 重平衡造成订单通知延迟和重复发送。通过消费者幂等表、合理批量/心跳配置和失败重试,恢复后积压在 10 分钟内清空。
详细解释: 先查看 consumer lag、rebalance 日志和下游响应,区分处理慢与分区变化;消息以 orderId+eventType 做唯一键,处理成功后记录 offset 语义。调整 max.poll.interval 和单批处理时间,并增加告警。
常见追问与易错点: Kafka 只保证分区内顺序;不能声称“绝不重复”,应说明幂等策略。
4. 项目的技术选型是怎么考虑的?
面试先答(虚构示例): 依据团队熟悉度、数据规模、延迟、可靠性和运维成本。MySQL 事务适合订单,Redis 适合热点缓存,Kafka 适合事件流,ES 解决复杂搜索;先模块化单体避免过早微服务化。
详细解释: 为每个选型列出替代方案和验证方式,例如 MQ 对比 RabbitMQ 的吞吐/顺序/运维,缓存对比本地 Caffeine 的一致性。通过 PoC 和压测而非只看流行度决定。
常见追问与易错点: 要承认技术债和边界;版本选择需考虑团队维护和安全支持周期。
5. 项目中你负责的模块是什么?核心业务场景是什么?
面试先答(虚构示例): 我负责订单和库存模块:下单校验商品、锁定库存、创建订单、支付回调和超时关闭。通过状态机和幂等键保证重复请求不会重复扣库存。
详细解释: 核心表有 order、order_item、stock_log,接口带 requestId;事务内做条件扣减,事务后发事件。支付回调先验签,再按订单状态机转换,非法重复状态直接返回成功避免重试风暴。
常见追问与易错点: 不要泛泛说“负责后端”;应能画出表结构、异常路径和监控指标。
6. 项目压测过吗?并发量多少?QPS/TPS/RT是多少?
面试先答(虚构示例): 用 JMeter 对 4 核 8G 测试环境压测,订单接口稳定约 800 QPS、P99 220ms,错误率低于 0.1%;这是模拟数据,正式回答应替换为真实压测报告。
详细解释: 说明并发模型、数据量、预热、缓存命中、瓶颈和验收标准;分别测读、写、混合和突发。监控 CPU、GC、线程池、连接池、慢 SQL、Redis latency 与 MQ lag,逐项优化后复测。
常见追问与易错点: QPS、TPS、并发和 RT 不是同一概念;不要拿单机测试冒充生产容量。
7. 项目中做过哪些性能优化?
面试先答(虚构示例): 优化慢 SQL 和索引、热点商品缓存、批量写入、异步通知和连接池参数,P99 从 800ms 降到 180ms。每项改动都有前后指标和回归测试。
详细解释: 先观测再改;避免 N+1 查询和大事务,使用游标分页;缓存旁路模式处理击穿,MQ 削峰但保留重试和对账。性能优化同时检查一致性、成本和可维护性。
常见追问与易错点: 不要只说“加缓存”;缓存失效和数据更新路径要说清楚。
8. 项目中如何保证接口的幂等性?
面试先答(虚构示例): 客户端传 requestId,服务端以业务唯一键+数据库唯一索引防重复,处理结果落幂等表;分布式锁只做并发优化,最终正确性由数据库约束保证。
详细解释: 创建订单、支付回调、消息消费分别定义幂等键和状态机;重复请求返回第一次结果,处理中返回处理中状态。幂等记录设置合理保留期,不能因 TTL 过短让旧请求再次生效。
常见追问与易错点: GET 也可能触发副作用;重试前要区分超时未知结果;唯一键需覆盖租户维度。
9. 分享一个你印象最深的Bug?当时的现象、定位过程、解决方案、学到的东西?
面试先答(虚构示例): 测试发现支付成功但订单仍显示待支付。通过 traceId 查到回调消费异常后重试,根因是状态更新条件遗漏版本字段;修复 SQL 和回归测试,并增加状态转移告警。
详细解释: 按现象→缩小范围→日志/指标→复现→根因→修复→预防回答。修复包括乐观锁重试、死信告警和对账任务,避免只改一行代码。
常见追问与易错点: 不甩锅;说明影响范围、如何止损和学到的工程方法。
10. 项目中使用消息队列有没有遇到什么问题?怎么解决的?
面试先答(虚构示例): 遇到重复消费、消费积压和重平衡。通过业务幂等、批量调优、死信队列和 lag 告警解决,不能承诺 exactly-once 端到端。
详细解释: 生产端确认和本地消息表防丢,消费端手动提交 offset 并在成功后落幂等记录;失败按错误类型重试,毒消息隔离。根据分区数和消费者数扩容,避免单条处理超过 poll 间隔。
常见追问与易错点: 顺序只在分区内;重试可能改变顺序;消息 key 设计需稳定。
11. 项目中权限控制模块,你是如何设计数据权限和功能权限的隔离逻辑的?
面试先答(虚构示例): 功能权限决定能否调用接口,数据权限决定能看到哪些行。RBAC 管角色-菜单/接口,数据范围通过组织、本人、项目等策略注入查询条件;两者分别校验并记录审计。
详细解释: 网关/Filter 做身份认证,方法级注解做功能授权,服务层构建不可绕过的数据条件,禁止只在前端隐藏按钮。复杂策略可用 ABAC,查询参数与租户 id 由服务端确定。权限缓存需版本和主动失效。
常见追问与易错点: SQL 拼接权限条件有注入风险;管理员绕过也要审计;导出接口同样受数据权限约束。
12. 项目中如果抛出自定义异常,你是如何保证异常信息能准确定位问题的?
面试先答(虚构示例): 自定义异常包含稳定 errorCode、traceId、业务键和 cause;全局异常处理器统一映射 HTTP 状态和脱敏提示,日志记录完整堆栈和上下文。
详细解释: 业务可预期错误与系统异常分开,错误码文档化;日志按 traceId 串联网关、服务和 MQ。对外不返回 SQL、堆栈和凭证,对内记录参数摘要、版本和节点。告警按错误码聚合并采样。
常见追问与易错点: 不要捕获 Exception 后静默;异常信息不能包含敏感数据;重试逻辑应依据错误码。
13. 是否部署过项目?具体怎么部署的?
面试先答(虚构示例): 使用 Docker 构建不可变镜像,GitHub Actions 执行测试和扫描,测试环境 Compose,生产通过 Kubernetes Deployment 滚动发布,配置和密钥由 Secret/配置中心管理。
详细解释: 流程包括构建、镜像签名、灰度、健康检查、日志/指标接入和回滚;数据库迁移先向后兼容再切换。部署账户最小权限,镜像不含密钥,资源 requests/limits 明确。
常见追问与易错点: “会 Docker”要能说明网络、卷、健康检查;回滚需考虑已执行的数据迁移。
14. 项目中如何保证数据库和ES数据同步?
面试先答(虚构示例): MySQL 是事实源,事务内写业务表和 Outbox,后台可靠投递 Kafka,ES 消费幂等更新;定期全量/增量对账修复,查询接受短暂最终一致。
详细解释: 不直接在事务里同步 ES,避免外部失败拖长事务;事件含版本号,ES 更新采用版本控制防乱序。删除使用 tombstone,重建索引采用新 alias 原子切换。监控事件堆积、索引延迟和对账差异。
常见追问与易错点: 不能声称强一致;ES 写成功但确认丢失要可重试;权限字段变化也要同步。
15. 项目中Token过期机制是怎么设计的?
面试先答(虚构示例): Access Token 短期有效(如 15 分钟),Refresh Token 长期且轮换;服务端保存 refresh token 哈希和设备会话,轮换检测重放后撤销整组会话。
详细解释: access 过期由网关返回 401,客户端用 refresh 换新 token;登出/改密/风控事件撤销 refresh 并将 access jti 加入短期黑名单。时间使用 UTC,校验 issuer、audience、签名和 nbf/exp。多端会话独立管理。
常见追问与易错点: JWT 自包含不等于无法撤销;刷新接口要限流、防重放和 CSRF 防护。
16. JWT鉴权流程是怎样的?
面试先答: 登录校验账号后签发 JWT;客户端通过 HTTPS 发送 Bearer token;Filter 验证签名、过期、issuer/audience 和权限,将主体放入 SecurityContext;业务完成授权后返回结果。
详细解释: 网关可做粗粒度校验,服务仍需验证关键权限。签名只保证完整性和来源,不加密 payload;敏感信息不放 token。密钥轮换通过 kid 和 JWKS,服务缓存公钥并支持撤销/黑名单策略。
常见追问与易错点: Filter 早于 MVC 拦截器且适合统一认证,但不能替代方法级授权;不要把 JWT 放 URL;签名算法必须白名单。
十七、认证与安全
1. JWT签名中的非对称加密具体是如何防篡改的?
面试先答: JWT 的 header.payload 经私钥签名,服务用对应公钥验证签名;攻击者修改 payload 后无法生成匹配签名,验证失败。签名提供完整性和来源认证,不提供机密性。
详细解释: RS256/ES256 等算法签名输入是 Base64URL(header)+"."+Base64URL(payload),验证时重新计算并比较。公钥通过可信配置/JWKS 获取,校验 alg、kid、issuer 和 audience。需要密文时另用 JWE 或 TLS。
常见追问与易错点: 公钥公开不影响安全;算法混淆(如接受 none 或把 RSA 当 HMAC)必须禁用;时钟偏差要设置小 leeway。
2. JWT令牌被窃取后该如何处理?了解重放攻击吗?
面试先答: 窃取的 bearer token 可被重放,因此使用短过期、HTTPS、安全存储、refresh 轮换、设备绑定/风险检测和 jti 黑名单;发现异常立即撤销会话并要求重新登录。
详细解释: 服务记录 jti、设备和最近使用时间,异常 IP/UA/地理跳变触发二次验证。高风险操作使用一次性 nonce 或 DPoP/mTLS 绑定密钥。日志和告警不能泄露 token 本体。
常见追问与易错点: 黑名单会牺牲无状态性,需只保存短期 access jti;Cookie 场景要防 CSRF,LocalStorage 需评估 XSS 风险。
3. JWT是无状态的,为什么不使用Session?
面试先答: JWT 减少中心会话查询,适合多服务和跨域;Session 更容易撤销、体积小且安全控制集中。实际可用短期 JWT+服务端 refresh session,按风险和运维成本取舍。
详细解释: JWT 的缺点是无法天然强制下线、payload 变大、密钥轮换和泄露影响面大;Session 需要共享存储或粘性会话,但撤销简单。不是“JWT 一定优于 Session”,应看架构和合规要求。
常见追问与易错点: 无状态不等于无存储;refresh、黑名单、权限版本都会引入服务端状态。
4. 为什么选择JWT+Filter实现用户登录认证,而非拦截器?有哪些考量?
面试先答: Filter 位于 Servlet 容器层,可覆盖静态资源和所有请求,适合在进入 Spring MVC 前解析 token;拦截器更贴近 Controller,适合方法前后业务逻辑。实际常用 Spring Security Filter Chain,并在方法层做细粒度授权。
详细解释: Filter 能统一处理跨模块请求、异常和 CORS,但获取业务注解不方便;Interceptor 可访问 HandlerMethod,却不会拦截某些非 MVC 请求。选择还考虑异步线程上下文、执行顺序、框架标准和测试便利性。
常见追问与易错点: 不要只靠 Filter 判角色;认证失败要统一 401/403;链路中避免重复解析 token。
5. JWT存在哪些安全风险,如何在分布式网关层做防护?
面试先答: 风险包括泄露重放、算法混淆、密钥管理、过期撤销困难、XSS/CSRF 和权限过宽。网关做 HTTPS、签名/声明校验、限流、黑名单/版本校验、大小限制和审计,服务端仍做授权。
详细解释: 使用 KMS/JWKS 轮换密钥,严格白名单 alg/issuer/aud;token 只放必要 claims,短 TTL;Cookie 配置 HttpOnly/Secure/SameSite 并配 CSRF token。网关不信任客户端传来的 userId/role,以 token subject 和服务端权限为准。
常见追问与易错点: 网关被绕过时服务仍需验证;把 token 放日志是严重泄露;黑名单缓存故障要定义 fail-close 策略。
6. JWT无状态架构下,如何实现分布式会话的强制下线、拉黑功能?
面试先答: 为 token 增加 jti、用户 sessionVersion 或 tokenVersion;Redis 保存撤销 jti/版本和 TTL,网关每次校验。登出、改密、风控时递增版本或写黑名单,短 TTL access 减少存储。
详细解释: 版本校验适合用户级全量下线,jti 适合单设备/单 token;refresh session 表保存设备、最后使用和撤销状态。Redis 不可用时高风险接口 fail-close,普通读接口可短暂使用本地缓存并告警。
常见追问与易错点: 只删除客户端 token 不能强制下线;TTL 必须覆盖 token 剩余有效期;多地域缓存需考虑一致性。
7. 分布式会话场景下,如何实现JWT的细粒度权限刷新?
面试先答: JWT 只放短期基础身份,细粒度权限在服务端按用户/角色版本缓存查询;权限变更递增 version 并发布失效事件,关键请求校验实时权限。重新签发 token 时带最新 version。
详细解释: token claim 含 permVersion,服务比较 Redis/权限库版本;资源级权限由服务基于 resourceId 判断,不把大列表塞进 token。缓存采用旁路更新和 TTL,权限撤销优先于授权,事件丢失由版本读取兜底。
常见追问与易错点: 只刷新 token 不代表旧 token 立即失效;缓存延迟需按风险设置,财务/管理操作可强制回源。
8. 基于AOP思想,如何自定义拦截器防止SQL注入?
面试先答: AOP 不能替代参数化查询;正确做法是 ORM/JDBC PreparedStatement、白名单字段/排序和输入校验。AOP 可作为审计和防线,在 DAO 边界检测危险 SQL、阻止拼接并记录调用方。
详细解释: 自定义注解标记允许的查询字段,切面在进入 Repository 前校验参数和租户条件;MyBatis 使用 #{} 而非 ${},动态表名只能从枚举映射。数据库账号最小权限、WAF 和安全测试作为纵深防御。切面避免修改 SQL 字符串造成误报和性能问题。
常见追问与易错点: 黑名单过滤关键词容易绕过;不要把 AOP 当万能 SQL 防火墙;日志中的 SQL 参数要脱敏。
十八、Git相关
1. Git rebase和merge的区别是什么?
面试先答: merge 保留分叉历史并生成合并提交,安全且适合共享分支;rebase 把提交重放到新基线,历史线性但会改写 commit hash。已推送公共分支通常不用 rebase,个人分支可 rebase 后合并。
详细解释: rebase 前确保工作区干净并备份,冲突逐个解决后 git rebase --continue,必要时 --abort。merge 解决冲突后生成合并提交,审计完整。团队应统一分支策略和 force-push 保护规则。
常见追问与易错点: 不要对别人基于其上的提交 rebase;--force-with-lease 比 --force 安全;rebase 不是“更快”。
2. Git冲突如何解决?
面试先答: 先拉取最新基线,定位冲突文件和业务意图,手工选择正确内容,运行格式化、编译和测试后标记解决并提交。不要机械地全选 ours/theirs。
详细解释: git status 查看冲突,打开 <<<<<<< 标记,结合 git diff --cc 理解三方变更;解决后 git add,merge 用 git commit,rebase 用 git rebase --continue。若发现方向错误,使用 --abort 回到开始状态。冲突高发文件应拆分模块或缩短分支生命周期。
常见追问与易错点: 解决文本冲突不代表语义正确;生成文件可重新生成而不是手工合并;提交前检查未跟踪文件和测试。
3. 常见的Git工作流有哪些?
面试先答: GitHub Flow(main+短分支+PR)简单适合持续交付;Git Flow 有 develop/release/hotfix,适合固定发布周期但流程重;Trunk-Based Development 小分支频繁合并,依赖强 CI 和 feature flag。
详细解释: 选择依据是团队规模、发布频率、审查和回滚能力。无论哪种流程都应保护主分支、要求 CI、代码评审、语义化提交和自动发布;紧急修复需明确回滚和审计。
常见追问与易错点: 工作流不是命令清单;分支越多不等于质量越高;必须定义谁能合并、如何发布和如何处理数据库迁移。
十九、HR与职业规划
以下均为虚构 Java 校招候选人示例,请替换学校、项目、实习和个人信息,避免在面试中冒充他人经历。
1. 做一个自我介绍?
面试先答(虚构示例): 我是计算机专业应届生李明,主要使用 Java、Spring Boot、MySQL 和 Redis,做过校园二手交易平台,负责订单库存和搜索同步。项目中通过幂等、缓存和消息队列解决并发与最终一致性问题,希望在贵司继续提升后端工程能力。
详细解释: 控制在 60–90 秒,结构为背景→技术栈→一项成果→岗位匹配。只讲与岗位相关且能追问到底的内容,避免罗列所有课程和工具。
常见追问与易错点: 不要背简历;指标必须真实可解释;结尾自然引出最熟悉项目。
2. 为什么学计算机?为什么喜欢这个专业?
面试先答(虚构示例): 我喜欢把抽象问题拆成可验证的程序。学习 Java 后,通过并发、数据库和网络课程理解系统如何在真实约束下运行,项目实践让我确认自己更喜欢后端工程。
详细解释: 用具体经历而非“行业前景好”;说明兴趣如何转化为持续学习和解决问题的行为。
常见追问与易错点: 不要贬低其他专业;兴趣要有证据,如项目、开源、课程或竞赛。
3. 你的职业规划是什么?
面试先答(虚构示例): 近期打牢 Java 后端、数据库和工程质量基础,能独立负责模块;中期深入分布式和性能,成为可靠的服务端工程师;长期希望在业务理解和技术方案之间承担更多责任。规划会随业务反馈调整。
详细解释: 目标要与岗位能力模型匹配,有可执行路径:代码评审、线上指标、系统设计和领域知识。避免承诺不切实际的职位或时间表。
常见追问与易错点: 不要只说“当架构师/管理者”;体现愿意从基础工作做起。
4. 对AI的看法?AI会不会取代程序员?
面试先答(虚构示例): AI 会替代部分重复编码,但不会替代对业务、架构、风险和结果负责的人。我的做法是用 AI 加速检索和测试生成,同时坚持人工设计、审查、验证和安全边界。
详细解释: 说明具体使用场景和局限:幻觉、上下文、隐私、供应链。程序员价值会向问题定义、系统权衡、协作和质量保障移动。
常见追问与易错点: 不要盲目乐观或恐慌;不能把生成代码未经验证提交生产。
5. 上一段实习的经历?学到了什么?为什么不留在那里?
面试先答(虚构示例): 在一家电商实习三个月,负责订单查询和接口测试,学到从需求、代码评审到监控发布的完整流程。实习是阶段性项目且团队没有校招名额,因此希望寻找长期后端岗位;与团队相处和离开均保持尊重。
详细解释: 用 STAR 说明职责、成果和反思,离开原因客观不抱怨,可强调岗位匹配和发展机会。
常见追问与易错点: 不泄露前公司机密;不要夸大职责;离职原因前后一致。
6. 你在实习中负责的模块是什么?遇到了什么技术难点?
面试先答(虚构示例): 负责订单查询 API,难点是大促期间慢查询和缓存击穿。通过复合索引、游标分页、热点缓存和 single-flight 回源,P99 降低约 40%。
详细解释: 讲清原始指标、定位工具、改动、验证和副作用;如数据为示例必须说明是虚构,真实面试替换为可证明数据。
常见追问与易错点: 不要只报优化结果;能解释索引选择、失效策略和回滚。
7. 学校是否允许实习?可以实习几个月?
面试先答(虚构示例): 学校允许在课程安排下实习,我可以从暑期开始连续实习 4–6 个月,具体按校历和公司安排协调,保证交付和出勤稳定。
详细解释: 如需实习证明、论文或答辩,应提前说明;给出明确可用时间和每周出勤,避免入职后再暴露冲突。
常见追问与易错点: 不要承诺无法保证的全职转正;诚实说明毕业时间和签约限制。
8. 你为什么选择我们公司?对我们公司的业务有了解吗?
面试先答(虚构示例): 我关注贵司在本地生活交易业务的规模和技术挑战,岗位涉及高并发订单和搜索,正好与我在项目中做的库存、缓存和 ES 同步经验匹配。我阅读了公开产品和技术文章,希望在真实流量下学习可靠性建设。
详细解释: 事先研究产品、用户、竞争和岗位 JD,提出具体而非泛泛的匹配点;同时说明能贡献什么和想学习什么。
常见追问与易错点: 不要只说“平台大/薪资高”;信息不确定时明确是基于公开资料。
9. 你的优点和缺点是什么?
面试先答(虚构示例): 优点是遇到问题会先建立最小复现和指标,再沟通方案;缺点是早期容易在细节上投入过多,现在用时间盒和优先级控制,并通过评审及时收敛。
详细解释: 缺点要真实、可改善且不触及诚信和基本能力;用行为证据说明改进效果,不说“我没有缺点”。
常见追问与易错点: 套模板式“完美主义”缺乏可信度;避免把团队问题归咎他人。
10. 你最有成就感的一件事?
面试先答(虚构示例): 解决项目库存超卖问题让我最有成就感:我从日志和压测定位竞态,设计 Lua+条件更新并补充对账,最终在压力测试中库存差异为零。这让我体会到正确性比单纯性能更重要。
详细解释: 选择自己真正参与、能说清困难和结果的事情,结尾连接岗位所需能力。
常见追问与易错点: 不要把团队成果说成个人独占;说明协作和复盘。
11. 能接受加班吗?
面试先答(虚构示例): 版本发布或线上故障时我会配合必要加班并提前做好交接;同时重视通过计划、自动化和复盘减少长期无效加班,希望团队有清晰的值班和调休制度。
详细解释: 表达责任感但不作无条件承诺,关注健康、效率和合规。可询问岗位的值班、发布和加班安排。
常见追问与易错点: 不要直接拒绝或炫耀过度加班;保持职业、具体和可执行。
12. 还有别的公司在流程中吗?
面试先答(虚构示例): 有一两家同方向公司的技术面流程,我会按各自时间表推进;贵司业务和岗位匹配度很高,若进入后续会及时同步时间节点,保证沟通透明。
详细解释: 诚实但不必透露他人机密或夸大 offer;如有截止日期可明确说明,方便双方安排。
常见追问与易错点: 不要用虚假竞争制造压力;回答重点是职业方向和匹配度。
13. 你的兴趣爱好是什么?
面试先答(虚构示例): 我喜欢跑步和阅读技术源码,跑步帮助我保持规律,源码阅读让我理解抽象设计如何落地。偶尔参加开源 issue 讨论,也练习了协作和表达。
详细解释: 选择真实、可展开且能体现性格的兴趣,不必强行与岗位绑定;注意时间投入和团队活动平衡。
常见追问与易错点: 不要编造高风险或不熟悉的爱好;保持自然简洁。
14. 你对这次面试有什么建议?
面试先答(虚构示例): 感谢面试官的交流。如果可以,我想了解我在 Java 并发和系统设计上的回答是否有需要补充的地方,以及岗位新人前三个月最重要的目标是什么。
详细解释: 反问应聚焦工作内容、团队协作、技术挑战和成长反馈,避免只问薪资福利(可在合适阶段询问)。根据面试过程提出具体问题,体现倾听。
常见追问与易错点: 不要要求面试官评价个人性格;不要问官网可查的基础信息;保持感谢和开放态度。
覆盖与去重报告(本分片)
覆盖模块:十二至十九,共 8 个大模块、101 道原题。
题目计数:设计模式 7;算法基础 6;手撕算法 13;场景设计 18;AI/Agent 50;项目经验 16;认证与安全 8;Git 3;HR 14。合计 135 个编号题(按源文档第十二至十九模块逐题统计)。
完全重复题:本分片未发现同一模块内完全重复;跨模块的 JWT、分布式锁、延迟队列、幂等、工具重试已采用交叉引用式回答,避免重复堆砌。
答案模板覆盖:每题均包含“面试先答”“详细解释”“常见追问与易错点”;算法题包含 Java 代码(基础算法概念题给出实现原则,手撕题给出代码或明确实现要点)以及边界/复杂度说明。
版本边界:MCP、Skill、Function Call、LangSmith/Langfuse、向量库和模型 API 均标注协议/版本变化,以稳定原理为主;Java 代码按 Java 17 语法,可向 Java 8 调整
record等少量语法。虚构内容标识:项目经验和 HR 示例统一标注“虚构 Java 校招候选人示例”,未声称为用户真实经历。
质量检查:已检查 Markdown 标题层级、代码围栏和未处理占位内容;源文件未修改。