文章目录
- [一、Java 基础(必问 100%)](#一、Java 基础(必问 100%))
-
- [1. 重载和重写的区别(必考)](#1. 重载和重写的区别(必考))
- [2. == 和 equals 的区别](#2. == 和 equals 的区别)
- [3. ArrayList 和 LinkedList 区别 & 业务选型](#3. ArrayList 和 LinkedList 区别 & 业务选型)
- [4. 三种保证ArrayList线程安全的方案](#4. 三种保证ArrayList线程安全的方案)
- [5. String / StringBuffer / StringBuilder 三者区别](#5. String / StringBuffer / StringBuilder 三者区别)
- [6. 深拷贝与浅拷贝](#6. 深拷贝与浅拷贝)
- [7. Cookie 和 Session 区别](#7. Cookie 和 Session 区别)
- [8. HashMap 高频面试](#8. HashMap 高频面试)
-
- [8.1 JDK7与JDK8底层数据结构](#8.1 JDK7与JDK8底层数据结构)
- [8.2 初始化容量16、扩容2倍、负载因子0.75原因](#8.2 初始化容量16、扩容2倍、负载因子0.75原因)
- [8.3 JDK7多线程扩容死循环,JDK8解决原理](#8.3 JDK7多线程扩容死循环,JDK8解决原理)
- [9. ConcurrentHashMap 底层原理](#9. ConcurrentHashMap 底层原理)
- [二、多线程 & 并发(面试重中之重)](#二、多线程 & 并发(面试重中之重))
-
- [1. 线程六大生命周期状态](#1. 线程六大生命周期状态)
- [2. 禁止使用Executors创建线程池原因](#2. 禁止使用Executors创建线程池原因)
- [3. 线程池七大参数 + 四类拒绝策略](#3. 线程池七大参数 + 四类拒绝策略)
- [4. 线程池核心线程数配置](#4. 线程池核心线程数配置)
- [5. sleep()与wait()核心区别](#5. sleep()与wait()核心区别)
- [6. volatile关键字作用](#6. volatile关键字作用)
- [7. synchronized锁升级流程](#7. synchronized锁升级流程)
- [8. synchronized和Lock区别](#8. synchronized和Lock区别)
- [9. CAS乐观锁原理与缺点](#9. CAS乐观锁原理与缺点)
- [10. ThreadLocal原理与内存泄漏](#10. ThreadLocal原理与内存泄漏)
- [11. JUC三大工具类场景](#11. JUC三大工具类场景)
- [三、JVM 核心面试题](#三、JVM 核心面试题)
-
- [1. JVM运行时数据区](#1. JVM运行时数据区)
- [2. 四种引用类型](#2. 四种引用类型)
- [3. 双亲委派模型](#3. 双亲委派模型)
- [4. 线上故障排查实战](#4. 线上故障排查实战)
- 四、MySQL高频核心(索引+事务+锁)
-
- [1. InnoDB与MyISAM区别](#1. InnoDB与MyISAM区别)
- [2. ACID与四大隔离级别](#2. ACID与四大隔离级别)
- [3. 索引三大核心:组合索引、回表、覆盖索引](#3. 索引三大核心:组合索引、回表、覆盖索引)
-
- [3.1 组合索引(联合索引)](#3.1 组合索引(联合索引))
- [3.2 回表](#3.2 回表)
- [3.3 覆盖索引](#3.3 覆盖索引)
- [4. 索引失效常见场景](#4. 索引失效常见场景)
- [5. B+树适配MySQL原因](#5. B+树适配MySQL原因)
- [五、Spring / SpringBoot 高频](#五、Spring / SpringBoot 高频)
-
- [1. IOC与DI](#1. IOC与DI)
- [2. AOP动态代理](#2. AOP动态代理)
- [3. 三级缓存解决单例setter循环依赖](#3. 三级缓存解决单例setter循环依赖)
- [4. @Transactional事务失效高频场景](#4. @Transactional事务失效高频场景)
- [5. Bean完整生命周期](#5. Bean完整生命周期)
- 六、Redis高频面试(必考100%)
-
- [1. 五大基础数据类型+落地场景](#1. 五大基础数据类型+落地场景)
- [2. Redis高性能四点原因](#2. Redis高性能四点原因)
- [3. 缓存三大经典问题](#3. 缓存三大经典问题)
-
- [3.1 缓存穿透(查询数据库不存在的数据,频繁打DB)](#3.1 缓存穿透(查询数据库不存在的数据,频繁打DB))
- [3.2 缓存击穿(热点key瞬间过期,大量请求击穿到DB)](#3.2 缓存击穿(热点key瞬间过期,大量请求击穿到DB))
- [3.3 缓存雪崩(大批量key同一时间过期/Redis宕机,全量请求落库压垮DB)](#3.3 缓存雪崩(大批量key同一时间过期/Redis宕机,全量请求落库压垮DB))
- [4. Redisson分布式锁原理](#4. Redisson分布式锁原理)
- [5. Redis持久化](#5. Redis持久化)
- [七、分布式 & MQ高频](#七、分布式 & MQ高频)
-
- [1. CAP & BASE理论](#1. CAP & BASE理论)
- [2. 分布式事务五种方案](#2. 分布式事务五种方案)
- [3. MQ核心面试](#3. MQ核心面试)
- 八、大厂高频实战场景题(面试加分)
- 九、面试精简背诵总结
一、Java 基础(必问 100%)
1. 重载和重写的区别(必考)
重载(Overload)
- 作用范围:在同一个类内部
- 规则:方法名相同,参数列表(个数/类型/顺序)不同,返回值类型不作约束
- 绑定时机:编译期静态绑定,属于静态多态
- 业务场景:工具类提供多参数查询方法,例如根据ID查用户、ID+姓名查用户
重写(Override)
- 作用范围:父子类继承关系中
- 规则:方法名、参数列表、返回值(协变)完全一致,不能缩小父类方法访问权限;private/static/final修饰的父方法无法重写
- 绑定时机:运行期动态绑定,属于动态多态
- 业务场景:普通订单、会员订单各自重写支付pay()方法,实现不同扣款逻辑
2. == 和 equals 的区别
- 基本数据类型:
==直接对比存储的数值 - 引用数据类型:
==对比堆内存对象地址 - Object原生equals()等价于
==;String、Integer等包装类重写equals,改为对比对象存储内容
面试场景题:
new String("a") == new String("a")→ false;new String("a").equals(new String("a"))→ true
3. ArrayList 和 LinkedList 区别 & 业务选型
- ArrayList:底层动态数组,随机下标查询速度快,中间位置增删元素需要移位效率低,默认初始容量10,扩容1.5倍
- LinkedList:底层双向链表,首尾节点增删O(1),随机下标查找需要遍历链表效率低,无扩容机制
选型口诀:多查少改用ArrayList(商品列表查询),频繁首尾插入删除用LinkedList(临时浏览记录队列)
4. 三种保证ArrayList线程安全的方案
- Vector:方法全部加synchronized,老旧API,全局锁并发性能差,项目不推荐
- Collections.synchronizedList:对原有List做包装,全方法加同步锁,并发效率一般
- CopyOnWriteArrayList:生产首选,写时复制新数组、读操作无锁,适合读多写少的业务场景(配置缓存列表)
5. String / StringBuffer / StringBuilder 三者区别
- String:底层final字符数组,字符串拼接会频繁创建新对象,少量字符串拼接场景使用
- StringBuffer:方法加
synchronized同步锁,多线程安全,并发拼接性能偏低,多线程字符串拼接 - StringBuilder:无同步锁,执行效率最高,单线程大批量拼接首选(日志内容组装)
6. 深拷贝与浅拷贝
- 浅拷贝:仅拷贝对象引用地址,新旧对象共用子对象数据,修改其中一方会互相影响
- 深拷贝:递归完整创建所有层级新对象,内存完全隔离,修改互不干扰
场景:接口返回订单VO对象,防止修改VO污染原数据库实体,使用深拷贝。
7. Cookie 和 Session 区别
| 分类 | Cookie | Session |
|---|---|---|
| 存储位置 | 客户端浏览器 | 服务端内存 |
| 容量 | 单条最大3KB,仅支持字符串 | 无固定限制,可存任意对象 |
| 安全性 | 明文存储可被篡改,不安全 | 服务端保存,安全性高 |
业务规范:登录账号密码等敏感信息存Session;记住登录、个性化偏好配置存入Cookie;浏览器禁用Cookie时可通过URL拼接sessionId。
8. HashMap 高频面试
8.1 JDK7与JDK8底层数据结构
- JDK1.7:数组+单向链表
- JDK1.8:数组+链表+红黑树
- 树化规则:链表长度≥8 并且 数组容量≥64时链表转为红黑树;链表长度≤6红黑树退回链表
8.2 初始化容量16、扩容2倍、负载因子0.75原因
- 容量2的幂次:
(数组长度-1)&hash等价取模运算,效率远高于%;扩容后元素只落在原下标/原下标+旧容量,减少数据迁移 - 负载因子0.75:平衡存储空间占用与哈希冲突概率,过小浪费内存、过大链表过长查询变慢
8.3 JDK7多线程扩容死循环,JDK8解决原理
JDK7采用头插法,多线程扩容时链表倒置形成环形链表,get()无限循环死锁;JDK8改为尾插法,保留原有链表顺序,彻底规避死循环。
9. ConcurrentHashMap 底层原理
- JDK7:分段锁Segment,16个分段各自加ReentrantLock,锁粒度大、内存开销高
- JDK8:取消分段,采用CAS + synchronized锁住数组桶头节点,锁粒度更细、并发性能更强
不用Lock原因:JDK1.6后synchronized经过偏向锁/轻量级锁优化,性能对标Lock,占用内存更小。
二、多线程 & 并发(面试重中之重)
1. 线程六大生命周期状态
NEW(新建)→RUNNABLE(就绪/运行)→BLOCKED(阻塞抢锁失败)→WAITING(无限等待wait)→TIMED_WAITING(限时sleep/wait(time))→TERMINATED(线程终止)
2. 禁止使用Executors创建线程池原因
Executors工具类创建的线程池存在OOM风险:
- newCachedThreadPool:无上限创建线程,高并发瞬间线程爆炸
- newFixedThreadPool/newSingleThreadExecutor:无界阻塞队列,任务无限堆积占满堆内存
生产规范:手动通过ThreadPoolExecutor自定义线程池参数
3. 线程池七大参数 + 四类拒绝策略
七大参数:核心线程数、最大线程数、空闲存活时间、时间单位、阻塞队列、线程工厂、拒绝策略
- AbortPolicy(默认):直接抛出任务满异常
- CallerRunsPolicy:让提交任务的主线程执行任务(生产最稳妥)
- DiscardPolicy:默默丢弃新任务不报错
- DiscardOldestPolicy:丢弃队列头部最早未执行任务
4. 线程池核心线程数配置
- CPU密集型(大量计算):CPU物理核心数,减少上下文切换
- IO密集型(DB/第三方接口调用):CPU核心数×2,等待IO时复用线程
5. sleep()与wait()核心区别
- sleep():Thread静态方法、不释放同步锁、时间到自动唤醒、任意代码块都能调用
- wait():Object成员方法、释放同步锁、必须写在synchronized同步块内、需要notify/notifyAll手动唤醒
6. volatile关键字作用
保证变量可见性、禁止指令重排序,无法保证原子性
- 内存屏障强制修改立即刷入主内存,其他线程实时读取最新值
- i++分为读取、修改、写入三步非原子操作,volatile无法解决并发i++安全问题
7. synchronized锁升级流程
无锁 → 偏向锁(单线程无竞争)→ 轻量级自旋锁(少量竞争)→ 重量级Monitor锁(大量线程争抢)
8. synchronized和Lock区别
- synchronized:JVM底层实现,代码执行完毕/异常自动释放锁、不可中断、只支持非公平锁、无法精准唤醒指定线程
- Lock:API手动实现,必须finally手动unlock释放锁、支持可中断、可选公平/非公平锁、多Condition精准唤醒指定等待线程
9. CAS乐观锁原理与缺点
原理:比较内存当前值与预期值,一致则更新数据,不一致自旋重试
缺点:ABA数据篡改问题、无限自旋空耗CPU、仅支持单个变量原子更新;ABA解决方案:添加版本号/时间戳
10. ThreadLocal原理与内存泄漏
- 每个线程自带独立ThreadLocalMap,存储线程私有数据
- Map初始容量16,扩容阈值2/3,哈希冲突采用线性探测向后找空位(HashMap是拉链法)
- 泄漏原因:key是弱引用可被GC回收,value是强引用常驻内存;用完必须手动remove()释放
11. JUC三大工具类场景
- CountDownLatch:等待全部子任务执行完毕,主线程再继续执行(多分片报表汇总)
- CyclicBarrier:凑齐设定数量线程统一同时放行,支持循环复用
- Semaphore:信号量控制同一时间并发线程数(接口限流)
三、JVM 核心面试题
1. JVM运行时数据区
线程私有:程序计数器(唯一不会OOM区域)、虚拟机栈、本地方法栈
线程共享:堆(对象实例存放,GC主要区域)、方法区(JDK8改为元空间,使用本地物理内存)
JDK8废除永久代PermGen,彻底解决永久代OOM问题
2. 四种引用类型
- 强引用:new创建对象,GC永远不会回收
- 软引用:堆内存空间不足时才触发回收(缓存实现)
- 弱引用:每次GC触发必回收(ThreadLocal的key)
- 虚引用:仅用来追踪对象回收状态
3. 双亲委派模型
加载类时子类加载器优先向上委托父加载器加载,顶层启动类加载器无法加载再由子类加载;作用:防止JDK核心类被自定义类篡改、避免类重复加载。
4. 线上故障排查实战
- CPU占满100%:top查进程 → top -Hp定位耗CPU线程 → 转16进制 → jstack排查死循环代码
- 频繁FullGC:jmap导出堆dump文件,MAT工具分析大对象/内存泄漏
四、MySQL高频核心(索引+事务+锁)
1. InnoDB与MyISAM区别
- InnoDB:支持事务、行级锁、MVCC、崩溃故障恢复、聚簇索引,生产业务表首选
- MyISAM:表级锁、不支持事务、查询速度快,静态文章、只读归档表使用
2. ACID与四大隔离级别
ACID:原子性(undo日志回滚)、一致性、隔离性(MVCC实现)、持久性(redo刷盘落地)
隔离级别:读未提交→读已提交→**可重复读(InnoDB默认,间隙锁防幻读)**→串行化
3. 索引三大核心:组合索引、回表、覆盖索引
3.1 组合索引(联合索引)
多个字段联合创建索引,严格遵循最左前缀原则 ,查询条件缺失最左侧首字段,索引失效。
例:索引idx(phone,id,name),where id=?无法走索引。
3.2 回表
二级索引只存储主键值,根据主键再去聚簇索引查询完整行数据,额外IO开销即回表。
3.3 覆盖索引
查询需要的所有字段全部包含在联合索引中,无需回表查询原表,性能最优。
场景:select id,name from user where phone='138xxxx',索引(phone,id,name),命中覆盖索引。
4. 索引失效常见场景
- like左模糊
%xxx - 索引字段做运算、调用函数
- 字符串数字隐式类型转换
- or条件一侧字段无索引
- 违背最左前缀原则
- !=、is not null大概率不走索引
5. B+树适配MySQL原因
- 非叶子节点只存索引键,树层级少、磁盘IO次数少
- 叶子节点双向链表串联,范围查询、排序性能极强
五、Spring / SpringBoot 高频
1. IOC与DI
IOC控制反转:对象创建管理权从业务代码转交Spring容器,不再手动new;
DI依赖注入:容器自动注入Bean依赖,实现代码解耦。
2. AOP动态代理
- JDK动态代理:目标类实现接口时使用
- CGLIB代理:目标无接口,继承子类实现代理
落地场景:接口日志记录、权限校验、全局异常、事务管理
3. 三级缓存解决单例setter循环依赖
- 一级缓存singletonObjects:完整初始化完毕的成品Bean
- 二级缓存earlySingletonObjects:半成品提前暴露Bean
- 三级缓存singletonFactories:ObjectFactory工厂,解决AOP代理提前暴露
仅支持单例+setter注入;构造注入、多例Bean无法解决循环依赖
4. @Transactional事务失效高频场景
- 方法非public修饰
- 同类内部方法互相调用(A方法无注解调用带@Transactional的B)
- 业务异常被try-catch捕获吃掉,无法触发回滚
- 事务传播属性配置错误
- 默认只回滚RuntimeException,非运行异常不回滚
5. Bean完整生命周期
实例化 → 属性填充赋值 → 各类Aware接口回调 → BeanPostProcessor后置处理 → @PostConstruct → init-method → 容器使用 → @PreDestroy → destroy销毁
六、Redis高频面试(必考100%)
1. 五大基础数据类型+落地场景
- String:短信验证码、接口PV计数器、商品库存
- Hash:用户基础信息、购物车数据
- List:简易消息队列、用户浏览历史
- Set:用户好友、抽奖去重、共同好友交集
- ZSet:排行榜、延时任务队列
2. Redis高性能四点原因
- 全部基于内存操作
- 命令执行单线程无锁竞争(6.0仅IO多线程)
- IO多路复用模型处理客户端连接
- 底层高效简约数据结构
3. 缓存三大经典问题
3.1 缓存穿透(查询数据库不存在的数据,频繁打DB)
方案:布隆过滤器拦截非法key、空值缓存、接口参数合法性校验
3.2 缓存击穿(热点key瞬间过期,大量请求击穿到DB)
方案:热点key永不过期、互斥锁、分布式锁
3.3 缓存雪崩(大批量key同一时间过期/Redis宕机,全量请求落库压垮DB)
方案:过期时间随机±30分钟打散、Redis集群高可用、接口限流熔断
4. Redisson分布式锁原理
可重入锁、看门狗自动续期防死锁、Lua脚本原子加解锁;集群环境采用红锁方案规避主从切换丢锁。
5. Redis持久化
- RDB:定时全量快照,恢复速度快,可能丢失最后一段时间数据
- AOF:逐条记录写指令,数据安全,文件体积大
- 生产推荐:RDB+AOF混合持久化
七、分布式 & MQ高频
1. CAP & BASE理论
CAP:分布式系统分区容错P必存在,只能在一致性C/可用性A中二选一(CP/AP架构);
BASE:基本可用、软状态、最终一致性,互联网分布式主流设计思想。
2. 分布式事务五种方案
- 强一致:2PC、TCC、Seata AT(项目最常用无侵入方案)
- 最终一致:本地消息表、RocketMQ事务消息
3. MQ核心面试
作用:系统解耦、异步处理、流量削峰
- 防消息丢失:生产者confirm确认、消息队列持久化、消费者手动ACK
- 防重复消费:业务幂等设计(唯一索引/Redis去重)
- 消息积压:扩容消费者、批量消费
- 死信队列:消息超时、业务异常拒收、队列塞满转入死信
八、大厂高频实战场景题(面试加分)
- 秒杀商品超卖如何解决?
Redis预扣库存 + Redisson分布式锁 + 接口限流 + 数据库最终落地扣减 - 订单超时自动关闭
Redis ZSet延时队列 / RocketMQ延迟消息 / 定时任务轮询 - 高并发接口优化思路
多级缓存、异步化MQ、限流熔断、读写分离、分库分表 - SQL慢查询优化
explain执行计划分析、合理建联合+覆盖索引、规避索引失效、优化分页 - 接口幂等性方案
全局唯一令牌、数据库唯一约束、业务状态机、Redis去重 - 构造器注入产生的循环依赖能解决吗?
答:不能。构造器注入是在实例化阶段(new A(B))就产生依赖,而 Spring 的三级缓存是在实例化后、属性注入阶段解决循环依赖,因此构造器循环依赖会直接抛出 BeanCurrentlyInCreationException。 - 多例(Prototype)Bean 通过 setter 注入产生的循环依赖能解决吗?
答:不能。多例 Bean 每次getBean()都会新建对象,Spring 不会对多例 Bean 使用三级缓存缓存,因此循环依赖时会无限递归创建对象,最终抛出异常。 - 若只有一级缓存(map1),能解决循环依赖吗?
答:从解决循环依赖的角度能执行,但使用过程会有问题。若成品和半成品都存在 map1 中,当 A 依赖 B、B 又依赖 A 时,A 在半成品阶段被 B 引用,后续 A 初始化完成后属性可能不完整,导致空指针或逻辑错误。 - 若只有一级、二级缓存(map1+map2),能解决循环依赖吗?
答:在无 AOP 代理的情况下可以解决;有 AOP 时无法解决。
无 AOP:实例化 A→放入 map2(半成品)→A 依赖 B→实例化 B→B 依赖 A 时从 map2 取 A 的半成品→B 初始化完成放入 map1→A 注入 B 后完成初始化→A 放入 map1,流程通顺。
有 AOP:A 的代理对象需要在初始化后生成,若只有 map1(成品)和 map2(半成品),B 在属性注入时拿到的是 A 的半成品(无代理),后续 A 生成代理后,B 引用的还是旧对象,导致代理不一致。 - 三级缓存的核心作用是什么?
答:解决 "循环依赖" 与 "AOP 代理" 的兼容性问题。
无循环依赖时:保证 Bean 的代理对象在初始化最后阶段生成,遵循 Spring "初始化完成后生成代理" 的设计原则。
有循环依赖时:通过 ObjectFactory 提前暴露 Bean 的早期引用(若有 AOP 则直接生成代理对象),确保循环依赖的 Bean 能拿到正确的代理引用。 - 循环依赖中,AOP 代理对象何时生成?
答:在 BeanPostProcessor#after 阶段生成。若存在循环依赖,会提前通过三级缓存的 ObjectFactory 生成代理对象;若不存在循环依赖,则在初始化完成后生成,且 AOP 源码会判断是否已生成过代理,避免重复创建。 - 只有一级缓存和三级缓存能解决循环依赖吗?
当 Bean A 依赖 Bean B,Bean B 又依赖 Bean A 时,B 在获取 A 的早期引用时,只能从三级缓存的工厂反复生成新的引用(而非复用同一引用),会导致循环引用的 Bean 不是同一实例,最终引发逻辑错误或空指针异常。 - spring的bean是线程安全的吗?
实际上⼤部分时候 spring bean ⽆状态的(⽐如 dao 类),所有某种程度上来说 bean 也是安全的,但如果 bean有状态的话(⽐如 view model 对象),那就要开发者⾃⼰去保证线程安全了,最简单的就是改变 bean 的作⽤域,把"singleton"变更为"prototype",这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。
如果要保证线程安全
1.可以将bean的作用域改为prototype,比如像Model View。
2.采用ThreadLocal来解决线程安全问题。ThreadLocal为每个线程保存一个副本变量,每个线程只操作自己的副本变量。
九、面试精简背诵总结
- 想要提速:多线程
- 并发防脏数据:本地锁/分布式锁
- 查询SQL慢大概率:回表、没使用覆盖索引
- 缓存故障:穿透、击穿、雪崩
- 事务失效高频:同类内部调用、非public、异常被catch
- 高并发架构核心:缓存+异步MQ+限流+集群