临近面试,面对海量的 Java 后端知识点,你是否感到无从下手?HashMap、JVM、并发编程、MySQL、Redis、Spring……每个都是面试必考,但每个都深不见底。本文旨在为你提供一个高效、系统的“面试急救”复习路线,聚焦核心考点与高频面试题,每天投入约2小时,用7天时间串联起后端面试的核心知识体系。无论你是准备校招还是社招,都能从中找到复习重点和答题思路。
1. 复习总览与计划制定
在开始具体知识点复习前,制定一个清晰的计划至关重要。盲目地翻阅资料或刷题往往事倍功半。本计划将核心知识点拆解到7天,每天聚焦一个主题,确保复习的系统性和深度。
1.1 七天复习计划表
以下计划表为你勾勒出清晰的复习路径,每天主题明确,兼顾深度与广度。
| 天数 | 核心主题 | 重点内容 | 目标 |
|---|---|---|---|
| 第1天 | Java集合框架 (聚焦HashMap) | HashMap底层原理、扩容机制、线程安全问题、与HashTable/ConcurrentHashMap对比 | 深入理解HashMap,能清晰阐述其数据结构、哈希冲突解决及并发场景下的选择。 |
| 第2天 | JVM内存与垃圾回收 | JVM内存模型(堆、栈、方法区)、垃圾回收算法、垃圾收集器(CMS、G1)、常见OOM分析 | 掌握JVM内存布局,能说清楚对象创建到回收的全过程,并能初步分析内存溢出问题。 |
| 第3天 | Java并发编程 | 线程状态、synchronized、volatile、JUC包(AQS、ReentrantLock、并发容器、线程池) | 理解Java并发核心机制,掌握锁的使用场景,能合理使用JUC工具解决并发问题。 |
| 第4天 | MySQL核心原理 | 索引结构(B+树)、事务隔离级别、锁机制(行锁、间隙锁)、SQL优化、Explain使用 | 深入理解InnoDB存储引擎的工作原理,掌握SQL性能分析和优化的基本方法。 |
| 第5天 | Redis设计与应用 | 数据类型与应用场景、持久化机制、主从复制、哨兵与集群模式、缓存穿透/雪崩/击穿 | 掌握Redis核心特性,理解其在高并发场景下的作用及常见问题的解决方案。 |
| 第6天 | Spring框架核心 | IoC与AOP原理、Bean生命周期、事务管理、Spring MVC流程、常用注解 | 理解Spring框架的设计思想,掌握其核心功能的实现原理和使用方式。 |
| 第7天 | 项目与场景设计 | 项目难点梳理、系统设计思路(如秒杀、缓存、分布式ID)、线上问题排查复盘 | 整合前六天知识,用于描述项目、应对系统设计题,形成完整的知识输出能力。 |
1.2 复习方法论:从点到面
复习时切忌死记硬背。对于每个知识点,建议遵循“是什么 -> 为什么 -> 怎么用 -> 关联点”的思考路径。
- 是什么:准确定义概念。例如,HashMap是基于哈希表的Map接口实现。
- 为什么:理解设计动机和原理。例如,HashMap为什么用数组+链表/红黑树?为了解决哈希冲突并保证查询效率。
- 怎么用:掌握API和最佳实践。例如,如何正确使用HashMap的
key(重写hashCode和equals)。 - 关联点:进行横向对比和延伸。例如,HashMap vs ConcurrentHashMap, HashMap vs HashTable。
接下来,我们将按天深入各个核心主题。
2. 第1天:深入剖析HashMap
HashMap是Java集合框架的面试“钉子户”,必须彻底搞懂。
2.1 底层数据结构演进
HashMap在JDK 1.8前后有显著优化。
- JDK 1.7及以前:数组 + 单向链表。发生哈希冲突时,新元素采用头插法插入链表。
- JDK 1.8及以后:数组 + 单向链表 / 红黑树。当链表长度超过阈值(默认为8)且数组长度大于等于64时,链表会转换为红黑树;当树节点数小于6时,会退化为链表。新元素采用尾插法。
// HashMap 的 Node 节点定义(JDK 1.8+) static class Node<K,V> implements Map.Entry<K,V> { final int hash; // 哈希值 final K key; V value; Node<K,V> next; // 下一个节点,形成链表或树的子节点 // ... 构造方法和其他代码 }2.2 核心方法与流程分析
1.put(K key, V value)流程:
- 计算 key 的
hashCode(),并通过hash()方法进行二次哈希,以减少碰撞。 - 根据
(n - 1) & hash计算数组下标(n为数组长度)。 - 如果该位置为空,直接新建节点插入。
- 如果不为空(哈希冲突):
- 如果 key 相同(
hash相等且keyequals),则覆盖 value。 - 如果该节点是树节点,调用红黑树的插入方法。
- 如果是链表,遍历链表,找到相同 key 则覆盖,否则尾插法插入。插入后判断链表长度是否>=8,是则尝试树化。
- 如果 key 相同(
- 插入后,判断元素总数是否超过
容量 * 负载因子(默认0.75),超过则进行扩容。
2.get(Object key)流程:
- 计算 key 的 hash 值,找到数组下标。
- 如果该位置节点 key 匹配,直接返回。
- 如果不匹配,判断是链表还是红黑树,分别遍历查找。
3. 扩容机制:
- 触发条件:
size > threshold(threshold = capacity * loadFactor)。 - 过程:创建新数组(大小为原数组2倍),遍历旧数组每个元素,重新计算在新数组中的位置(
(e.hash & oldCap) == 0判断高位,优化了重新哈希的计算)。 - 并发问题:多线程同时执行
put并触发扩容,可能导致链表形成环,进而引起get操作死循环(JDK 1.7头插法问题)或数据丢失。
2.3 线程安全问题与替代方案
HashMap是非线程安全的。多线程并发修改可能导致数据不一致、死循环(JDK1.7)等问题。
- HashTable:线程安全,但通过在方法上加
synchronized实现,性能差,已不推荐使用。 - ConcurrentHashMap (JDK 1.8):采用
Node数组 + 链表/红黑树 + synchronized + CAS实现分段锁(粒度更细)。对数组元素(桶)的头节点加锁,大大提高了并发度。 - Collections.synchronizedMap(Map):返回一个包装后的线程安全Map,所有方法用
synchronized同步,性能一般。
面试题示例:
Q:HashMap为什么线程不安全?A:主要体现在并发扩容时可能造成链表成环(JDK1.7),导致死循环;以及多线程
put可能导致数据覆盖。其内部没有同步机制,修改size等操作非原子性。Q:ConcurrentHashMap在JDK1.7和1.8中实现有何不同?A:JDK1.7采用分段锁(Segment),每个Segment继承自ReentrantLock。JDK1.8摒弃了Segment,改用Node数组 + synchronized + CAS,锁粒度更细(锁住链表或红黑树的头节点),并发性能更高。
3. 第2天:JVM内存模型与垃圾回收
理解JVM是解决性能问题和线上故障的基础。
3.1 运行时数据区(内存模型)
JVM内存划分为以下几个主要区域:
- 程序计数器:线程私有,指向当前线程正在执行的字节码指令地址。
- Java虚拟机栈:线程私有,生命周期与线程相同。存储栈帧,每个方法调用对应一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
StackOverflowError和OutOfMemoryError与此区域相关。 - 本地方法栈:为Native方法服务。
- Java堆:线程共享,几乎所有的对象实例和数组都在这里分配内存。是垃圾回收的主要区域。可分为新生代(Eden, S0, S1)和老年代。
- 方法区(元空间):线程共享,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。JDK 1.8后,使用本地内存的元空间(Metaspace)替代了永久代(PermGen),避免了
OutOfMemoryError: PermGen space。
3.2 垃圾回收算法与收集器
1. 垃圾判定算法:
- 引用计数法:循环引用问题。
- 可达性分析算法:通过一系列“GC Roots”对象作为起点,向下搜索,所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象不可用。GC Roots包括:虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI引用的对象等。
2. 垃圾收集算法:
- 标记-清除:产生内存碎片。
- 复制算法:将内存分为两块,每次使用一块,垃圾回收时将存活对象复制到另一块。用于新生代(Eden和Survivor区)。
- 标记-整理:标记后,让所有存活对象向一端移动,然后清理掉边界以外的内存。用于老年代。
3. 经典垃圾收集器:
- Serial / Serial Old:单线程,新生代复制算法,老年代标记-整理。适用于客户端模式。
- ParNew:Serial的多线程版本,与CMS配合工作。
- Parallel Scavenge / Parallel Old:JDK8默认组合,关注吞吐量。
- CMS:以获取最短回收停顿时间为目标。过程:初始标记 -> 并发标记 -> 重新标记 -> 并发清除。缺点:产生内存碎片、对CPU资源敏感。
- G1:面向服务端,将堆划分为多个Region,可预测停顿时间。过程:初始标记 -> 并发标记 -> 最终标记 -> 筛选回收。JDK9及以后成为默认收集器。
3.3 常见OOM异常与排查
OutOfMemoryError: Java heap space:堆内存不足。可能原因:内存泄漏、堆大小设置不合理、存在大对象。OutOfMemoryError: Metaspace:元空间(方法区)不足。可能原因:加载了过多类(如动态生成类)、元空间大小设置过小。OutOfMemoryError: unable to create new native thread:创建线程数超过系统限制。StackOverflowError:线程栈深度过大,通常由无限递归引起。
排查工具:jps(查看进程),jstat(查看GC情况),jmap(生成堆转储),jstack(生成线程快照),VisualVM,MAT(内存分析工具)。
4. 第3天:Java并发编程精要
并发是后端开发的核心能力,也是面试难点。
4.1 线程基础与同步机制
- 线程状态:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED。
synchronized:Java关键字,内置锁(监视器锁)。可以修饰实例方法(锁当前实例)、静态方法(锁当前类的Class对象)、代码块(需指定锁对象)。JDK1.6后进行了优化,引入了偏向锁、轻量级锁、重量级锁的升级过程。volatile:保证变量的可见性和禁止指令重排序,但不保证原子性。常用于状态标志位。
4.2 JUC(java.util.concurrent)包核心组件
1. AQS(AbstractQueuedSynchronizer): 并发包中锁(如ReentrantLock)和同步器(如CountDownLatch)的底层框架。核心是一个FIFO队列和一个state状态变量。通过tryAcquire、tryRelease等模板方法实现自定义同步器。
2. 锁
ReentrantLock:可重入锁,比synchronized更灵活,支持公平/非公平锁、可中断、超时等待、条件变量(Condition)。
Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); lock.lock(); try { while (!conditionMet) { condition.await(); // 释放锁并等待 } // 业务逻辑 condition.signal(); // 唤醒一个等待线程 } finally { lock.unlock(); }3. 并发容器
ConcurrentHashMap:如前所述。CopyOnWriteArrayList:写时复制,适合读多写少的场景。BlockingQueue:阻塞队列,是生产者-消费者模型的经典实现。如ArrayBlockingQueue(有界)、LinkedBlockingQueue(可选有界)、SynchronousQueue(不存储元素)。
4. 线程池
- 核心参数:
corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(空闲线程存活时间)、workQueue(工作队列)、threadFactory(线程工厂)、handler(拒绝策略)。 - 工作流程:
- 提交任务。
- 如果运行线程数 < corePoolSize,创建新线程执行。
- 否则,将任务放入workQueue。
- 如果队列已满且运行线程数 < maximumPoolSize,创建新线程执行。
- 否则,触发拒绝策略。
- 拒绝策略:AbortPolicy(抛异常)、CallerRunsPolicy(调用者运行)、DiscardOldestPolicy(丢弃最老任务)、DiscardPolicy(丢弃当前任务)。
- 常用线程池:
Executors.newFixedThreadPool(固定大小)、newCachedThreadPool(可缓存)、newSingleThreadExecutor(单线程)、newScheduledThreadPool(定时任务)。注意:newFixedThreadPool和newSingleThreadExecutor使用的无界队列可能导致OOM,生产环境建议使用ThreadPoolExecutor手动创建。
4.3 原子类与CAS
- CAS(Compare-And-Swap):一种无锁的乐观并发策略。包含三个操作数:内存位置(V)、预期原值(A)、新值(B)。当且仅当V的值等于A时,才会用B更新V的值,否则什么都不做。
sun.misc.Unsafe类提供了底层CAS操作。 - 原子类:
AtomicInteger、AtomicLong、AtomicReference等,基于CAS实现,保证了单个变量的原子性操作。
面试题示例:
Q:
synchronized和ReentrantLock的区别?A:1. 本质:synchronized是JVM层面的关键字,ReentrantLock是JDK层面的API类。2. 功能:ReentrantLock更灵活,支持公平锁、可中断、超时、多个条件变量。3. 性能:在竞争不激烈时synchronized有优化,竞争激烈时ReentrantLock通常性能更好。4. 释放:synchronized自动释放,ReentrantLock必须手动unlock()。Q:线程池的队列满了,且线程数达到
maximumPoolSize,新任务如何处理?A:会触发拒绝策略(RejectedExecutionHandler)。默认是AbortPolicy,抛出RejectedExecutionException。
5. 第4天:MySQL核心原理与优化
数据库是后端系统的基石,其原理和优化是面试重中之重。
5.1 存储引擎与索引
- InnoDB vs MyISAM:
- InnoDB:支持事务、行级锁、外键,采用聚簇索引,是MySQL 5.5后的默认引擎。
- MyISAM:不支持事务和行锁,支持表锁,采用非聚簇索引,适合读多写少的场景。
- B+树索引:
- InnoDB使用B+树作为索引数据结构。与B树相比,B+树非叶子节点只存键,叶子节点存数据且形成有序链表,更适合范围查询和磁盘IO。
- 聚簇索引:叶子节点存储整行数据。InnoDB表必须有且只有一个聚簇索引,通常是主键。若无主键,则选择第一个唯一非空索引,否则隐式创建一个ROWID。
- 非聚簇索引(二级索引):叶子节点存储主键值。查询时需要回表,即先查到主键,再用主键去聚簇索引查完整数据。
- 索引优化:
- 最左前缀原则:联合索引
(a, b, c),查询条件需包含最左列a才能生效。 - 避免在索引列上做计算、函数、类型转换。
- 选择性高的列适合建索引(区分度高的列)。
- 最左前缀原则:联合索引
5.2 事务与锁机制
- ACID特性:原子性(Undo Log)、一致性(应用层保证)、隔离性(锁/MVCC)、持久性(Redo Log)。
- 事务隔离级别(由低到高):
- 读未提交:可能脏读、不可重复读、幻读。
- 读已提交:解决脏读,但可能不可重复读、幻读。(Oracle默认)
- 可重复读:解决脏读、不可重复读,但可能幻读。(MySQL InnoDB默认,通过MVCC解决了大部分幻读)
- 串行化:解决所有问题,但性能最低。
- MVCC(多版本并发控制):InnoDB实现高并发事务的核心机制。通过
ReadView和Undo Log实现。每个事务在开始时生成一个ReadView,根据事务ID和Undo Log链找到符合其隔离级别可见性的数据版本。 - 锁:
- 行锁:锁住一行记录。InnoDB通过给索引项加锁实现。
- 间隙锁:锁住一个索引区间(开区间),防止其他事务在区间内插入新记录,解决幻读问题。
- 临键锁:行锁+间隙锁的组合。
- 意向锁:表级锁,用于快速判断表中是否有行被锁定。
5.3 SQL优化与Explain
- 优化步骤:定位慢SQL(慢查询日志) ->
EXPLAIN分析 -> 优化索引或SQL写法。 EXPLAIN关键字段:- type:访问类型,从好到坏:
system>const>eq_ref>ref>range>index>ALL。 - key:实际使用的索引。
- rows:预估需要扫描的行数。
- Extra:额外信息,如
Using index(覆盖索引)、Using temporary(使用临时表)、Using filesort(文件排序,需优化)。
- type:访问类型,从好到坏:
-- 示例:分析查询语句 EXPLAIN SELECT * FROM user WHERE name = '张三' AND age > 20;分析结果应关注type是否为ref或range,key是否使用了合适的索引。
6. 第5天:Redis深度解析与应用
Redis作为高性能缓存和数据结构服务器,是应对高并发的利器。
6.1 核心数据结构与使用场景
- String:缓存、计数器、分布式锁。
- Hash:存储对象(如用户信息),可部分更新。
- List:消息队列、最新列表、阻塞队列。
- Set:共同关注、抽奖、随机推荐。
- Sorted Set:排行榜、带权重的消息队列。
- Bitmaps/HyperLogLog/Geospatial:位图统计、基数统计、地理位置。
6.2 持久化与高可用
- RDB:在指定时间间隔生成数据集的时间点快照。优点:文件紧凑,恢复快。缺点:可能丢失最后一次快照后的数据。
- AOF:记录每个写操作命令。优点:数据丢失少(可配置每秒同步)。缺点:文件大,恢复慢。
- 混合持久化(Redis 4.0+):结合两者,AOF文件前半部分是RDB格式,后半部分是增量AOF。兼顾速度和数据安全。
- 主从复制:一个主节点,多个从节点。主写从读,用于读写分离和数据备份。
- 哨兵模式:监控主从节点,主节点故障时自动选举新主并通知客户端,实现高可用。
- 集群模式:数据分片(16384个槽)存储在不同节点,支持水平扩展和高可用。
6.3 缓存问题与解决方案
- 缓存穿透:查询一个不存在的数据,请求直达数据库。解决:布隆过滤器、缓存空对象(设置短过期时间)。
- 缓存击穿:某个热点key过期瞬间,大量请求击穿到数据库。解决:互斥锁(如Redis的
SETNX)、永不过期(逻辑过期)。 - 缓存雪崩:大量key在同一时间过期或Redis宕机,请求全部打到数据库。解决:设置随机过期时间、Redis集群高可用、服务降级/熔断。
- 数据一致性:缓存与数据库数据不一致。解决:先更新数据库,再删除缓存(Cache-Aside Pattern),或使用消息队列异步更新。
面试题示例:
Q:Redis为什么快?A:1. 基于内存操作。2. 单线程模型,避免了上下文切换和锁竞争(6.0后网络IO多线程)。3. 高效的数据结构(如跳表、压缩列表)。4. 使用I/O多路复用模型(epoll)。
Q:如何用Redis实现分布式锁?A:使用
SET key value NX PX milliseconds命令,保证设置值和过期时间的原子性。释放锁时,使用Lua脚本判断锁的value是否为自己设置的,再执行删除,防止误删。
7. 第6天:Spring框架核心原理
Spring是Java后端开发的事实标准,其核心思想必须掌握。
7.1 IoC与AOP
- IoC(控制反转):将对象的创建、依赖注入的控制权从程序代码转移到容器(如Spring IoC容器)。DI(依赖注入)是其实现方式。
- AOP(面向切面编程):将横切关注点(如日志、事务)与核心业务逻辑分离。核心概念:切面(Aspect)、连接点(Join Point)、通知(Advice)、切点(Pointcut)、织入(Weaving)。
- Bean生命周期:
- 实例化(Instantiation)
- 属性赋值(Populate)
- 初始化(Initialization):调用
BeanPostProcessor.postProcessBeforeInitialization-> 调用InitializingBean.afterPropertiesSet-> 调用自定义init-method-> 调用BeanPostProcessor.postProcessAfterInitialization - 使用(In Use)
- 销毁(Destruction):调用
DisposableBean.destroy-> 调用自定义destroy-method
7.2 事务管理
- 声明式事务:通过
@Transactional注解或XML配置。基于AOP实现。 - 传播行为(Propagation):定义事务方法在调用其他事务方法时,事务如何传播。常用:
REQUIRED(默认,有则加入,无则新建)、REQUIRES_NEW(新建事务,挂起当前)、NESTED(嵌套事务)。 - 隔离级别:与数据库隔离级别对应。
- 失效场景:
- 方法非
public。 - 自调用(同一个类中A方法调用B方法,B的
@Transactional失效)。 - 异常被捕获未抛出。
- 异常类型错误(默认只回滚
RuntimeException和Error)。
- 方法非
7.3 Spring MVC处理流程
- DispatcherServlet接收请求。
- 调用HandlerMapping获取处理请求的Controller和方法(Handler)。
- 调用HandlerAdapter执行Handler。
- Handler执行后返回ModelAndView。
- ViewResolver解析视图。
- 渲染视图,返回响应。
常用注解:@Controller,@RestController,@RequestMapping,@RequestBody,@ResponseBody,@Autowired,@Resource,@Value。
8. 第7天:项目复盘与系统设计
最后一天,将前六天的知识融会贯通,用于描述项目和应对设计题。
8.1 项目难点与亮点梳理
准备1-2个你深度参与的项目,按照STAR法则(情境、任务、行动、结果)组织语言。
- 难点:例如,高并发下的超卖问题、分布式事务一致性、慢SQL优化、缓存数据一致性、Full GC频繁等。
- 解决方案:你用了什么技术(如Redis分布式锁、RocketMQ事务消息、索引优化、本地缓存+Caffeine)?为什么选这个方案?权衡了什么?
- 量化结果:优化后,QPS提升多少?接口RT降低多少?GC频率下降多少?
8.2 经典系统设计题思路
示例:设计一个秒杀系统
- 需求分析:瞬时高并发、读多写少、防止超卖、保证系统可用性。
- 架构设计:
- 前端:静态化页面、按钮防重复点击、验证码。
- 网关:限流、熔断。
- 服务层:无状态化、横向扩展。
- 缓存:商品库存预热到Redis,使用Lua脚本保证扣减库存的原子性。
- 消息队列:下单请求写入MQ,异步削峰,下游服务消费并完成订单创建、库存扣减等。
- 数据库:库存扣减最终落地到数据库,可使用乐观锁或悲观锁,但压力已通过MQ缓解。
- 关键细节:
- 防超卖:Redis预减库存 + 数据库最终扣减 + 乐观锁版本号。
- 限流:网关层令牌桶/漏桶算法,接口层使用
Semaphore或线程池隔离。 - 降级:如果系统压力过大,关闭非核心功能(如评论)。
8.3 线上问题排查思路
当被问到“如何排查线上CPU飙升/内存泄漏/接口超时”时,给出系统化的排查路径:
- 定位问题:监控报警、日志错误信息。
- CPU飙升:
top->top -Hp [pid]找高CPU线程 ->jstack [pid]导出线程栈,将线程ID转为16进制去栈信息里找对应代码。 - 内存泄漏:
jstat -gcutil观察GC情况 ->jmap -histo:live [pid]或jmap -dump:live,format=b,file=heap.hprof [pid]生成堆转储 -> 用MAT分析。 - 接口超时:检查依赖服务(数据库、缓存、下游接口)状态、慢查询日志、网络状况、Full GC情况。
七天的高强度聚焦复习,旨在帮你构建一个清晰、连贯的后端知识图谱。面试不仅是知识的复述,更是解决问题思路的展现。在复习时,多问自己“为什么”,并尝试将不同模块的知识串联起来。最后,保持自信,在面试中清晰地表达你的思考过程,即使遇到不会的问题,也可以坦诚地给出自己的分析思路。祝你面试顺利!