Java 后端八股精讲一问一答,背得下也答得出 速查 追问链路 ⌂ 全部教程

附录速查与总索引

这里放三类东西:已经压缩到极限的复习材料(A.1 / A.2)、 面试现场用得上的表达工具(A.3 / A.4)、以及卡住时的求助路径(A.5)。 注意:A.1 的定位是「复习」不是「初学」——没读过正文就直接看表,等于只背了结论。

A.1四槽速记卡总表

一行一条,斜体追问是「最可能被接上的下一句」。复习时盖住后两列,只看问法自述。

#八股问法一句话答案追问
1为什么工程里几乎不用 LinkedList 复杂度一样但常数差一个量级:数组连续能命中缓存行。 ArrayList 为什么扩 1.5 倍?
2HashMap 的 get 为什么平均 O(1) 位运算算出桶下标直接寻址,桶内链表/红黑树兜底冲突。 最坏情况呢?
3容量为什么必须是 2 的幂 n-1 二进制全 1,与运算才不浪费桶、等价于取模。 那还要扰动函数做什么?
4负载因子为什么是 0.75 空间与冲突的交点;再大冲突陡增,再小频繁扩容。 扩容时元素怎么迁移?
5JDK 7 扩容为什么会死循环 头插法反转链表,并发迁移时形成环,get 时无限循环。 JDK 8 就安全了吗?
6树化阈值为什么是 8 和 6 8 来自泊松分布(概率约 6e-8);6 与 8 留空隙防抖动。 容量不够 64 时呢?
7ConcurrentHashMap 怎么保证线程安全 JDK 8:空桶 CAS、非空桶 synchronized 锁桶头。 size() 怎么数准?
8它为什么不允许 null 键和 null 值 并发下无法区分「不存在」与「值是 null」,二义性会埋雷。 那 HashMap 为什么允许?
9Java 是值传递还是引用传递 只有值传递;对象传的是引用的副本,能改内部状态、不能让实参改指向。 C++ 为什么能写 swap?
10String 为什么设计成不可变 常量池才能共享、hashCode 才能缓存、才能安全跨线程传递。 new String("abc") 创建几个对象?
11重写 equals 为什么必须重写 hashCode HashMap 先按 hashCode 找桶、再按 equals 认人,少一个就取不回来。 equals 的参数为什么必须是 Object?
12什么是类型擦除 泛型只在编译期生效,编译后擦到上界,运行时不保留具体类型。 Spring 怎么拿到泛型类型?
13Integer 128 == 128 为什么是 false 只有 -128~127 走 IntegerCache,超出范围各自 new,比的是地址。 那包装类怎么比值?
14类的初始化顺序 父静态 → 子静态(只一次)→ 父实例块 → 父构造 → 子实例块 → 子构造。 静态常量会触发类初始化吗?
15递归多深会栈溢出 默认栈 1MB,实测约 2.3 万层;帧越大越浅(多 3 个 long 掉到 7360)。 溢出了能 catch 吗?
16new Object() 占多少字节 16 字节:头 12(Mark Word 8 + 类型指针 4)+ 4 对齐填充。 为什么头是 12 不是 8?
17双亲委派为什么要打破 Tomcat 要应用隔离、JDBC 要跨加载器找驱动,都靠重写 loadClass 或 TCCL。 Bootstrap 在 Java 里是什么?
18线上 OOM 怎么排查 先看报错文本判断爆哪个区,再看 Full GC 后老年代降不降,定泄漏还是不够。 没 dump 怎么办?
19为什么用 B+ 树不用二叉树 瓶颈是磁盘 IO:一页放 1638 个指针,3 层撑到亿级,IO 只 3 次。 3 层到底能存多少行?
20什么是回表 二级索引叶子只有「索引列 + 主键」,拿到主键还得去聚簇索引再查一次。 怎么避免?
21最左前缀为什么成立 索引先按第 1 列排、相同再按第 2 列排,所以要用第 N 列,前 N-1 列必须等值。 范围条件放前面会怎样?
22深分页怎么优化 延迟关联(覆盖索引取 id)或游标分页(where id > lastId),后者不支持跳页。 为什么延迟关联就快了?
23三个现象怎么一句话分清 脏读=读到未提交;不可重复读=值变了;幻读=行数变了。 为什么幻读锁行解决不了?
24MVCC 靠什么实现 三个东西:隐藏列(DB_TRX_ID/DB_ROLL_PTR)、undo 版本链、ReadView。 ReadView 里存的是数据吗?
25RC 与 RR 的差别在哪 只有一个:ReadView 的创建时机。RC 每条 select 建一次,RR 事务首次快照读建一次并复用。 那 RR 为什么还要间隙锁?
26RR 到底防不防幻读 快照读防住了(MVCC),当前读要靠间隙锁;先快照读后当前读,仍会看到新行。 记录锁、间隙锁、next-key 什么关系?
27for update 为什么要走索引 不走索引要全表扫描,扫到的每行都加锁,行锁退化成表锁,还带上 supremum 伪记录。 线上遇到这种事故怎么快定位?
281213 和 1205 有什么区别 1213 死锁,整个事务被回滚;1205 锁等待超时,只回滚当前语句。 哪一种更危险?
29线程池提交任务时的扩容顺序 核心 → 队列 → 扩容到 max → 拒绝。core=2/max=4/queue=2 时第 3 个进队列、第 5 个才扩容、第 7 个被拒。 队列是无界的会怎样?
30为什么禁用 Executors 的工厂方法 newFixedThreadPool 用无界队列,任务堆积到 OOM 且 max 永不生效;newCachedThreadPool 的 max 是 Integer.MAX_VALUE,线程会爆炸。 那线程数该开多少?
31线程数怎么定 CPU 密集 N+1,IO 密集 N×(1+W/C)。公式只是起点,上线看活跃线程数与队列长度再调。 开多了为什么反而慢?
32AQS 到底是什么 一个 volatile int state + 一个 CLH 双向等待队列。Lock 里 state 是重入次数,Semaphore 是许可数,Latch 是剩余计数。 能看见这个队列吗?
33count++ 为什么不是原子的 编译成「读 → 加一 → 写回」三条指令。实测 8 线程各 10 万次,丢 3617 次(约 0.45%)。 换成 Atomic 就一定好吗?
34ThreadLocal 为什么要 remove() 线程池复用线程,ThreadLocalMap 跟着复用 → 后一个请求读到前一个请求的用户。key 是弱引用但 value 是强引用 → 还泄漏。 不 remove 一定出问题吗?
35Bean 生命周期的十个回调 构造 → 注入 → Aware → BPP.before → @PostConstruct → afterPropertiesSet → initMethod → BPP.after → @PreDestroy → destroy → destroyMethod。 BPP.after 为什么在 initMethod 之后?
36三级缓存各存什么 一级成品、二级半成品、三级 ObjectFactory。第三级只为推迟「要不要包代理」的决定,让代理在早期就生成好且只生成一次。 两级为什么不行?
37Spring 能解决哪种循环依赖 只有单例 + setter/字段注入。构造器(对象还不存在)、原型(没有单例池)全部启动失败。 Boot 2.6 之后还默认允许吗?
38@Lazy 是怎么断环的 注入的是 CGLIB 代理(类名带 $$SpringCGLIB$$),第一次调用才去容器取。把启动期错误推迟成运行期错误。 那生产上该用吗?
39原型 Bean 会被销毁吗 不会。实测两次 getBean 后 close(),@PreDestroy / DisposableBean / destroyMethod 零次执行。 那连接谁来关?
40两种代理怎么选 有接口 → JDK $Proxy(兄弟);无接口 → CGLIB $$SpringCGLIB$$(父子)。final 类/方法、private 方法拦不到。 代理一次多花多少?
41@Transactional 失效的六种 自调用、private、final、类不归 Spring 管、异常被吞、表不支持事务。前四条同一个原因:没走代理。 怎么一眼确认走了代理?
42什么异常才回滚 默认只有 RuntimeException / Error。受检异常要 rollbackFor;实测抛 Exception 时行数=1(没回滚)。 catch 住又不想留脏数据?
43REQUIRED 内层失败为什么连累外层 内层加入外层事务,抛异常时整个事务被标 rollback-only;外层 catch 后提交 → UnexpectedRollbackException。 换成 REQUIRES_NEW 呢?
44timeout 与事务成本 timeout 落到 Statement.setQueryTimeout:sleep 3s 不超时,select sleep(3) 1.03s 就抛。一次事务约 0.34 ms。 一万条循环插入要多花几秒?

A.2与算法专题的对照总索引

这张表是两套教程的「接线图」。左边是算法专题,右边是对应的八股主题。 刷到哪个专题,就顺手把右边那几条读了——这是把两套知识缝起来成本最低的方式。

算法专题对应八股主题缝在一起的那句话
02 · 哈希表 01 · 集合框架 两数之和的 O(1) 查找,就是 (n-1)&hash 那一次寻址。
05 · 链表 01 · 集合框架 链表题在练指针,工程里却几乎只用 ArrayList——差在缓存行。
14 · 位运算 01 · 1.4 寻址 哈希与位运算是同一件事:用掩码取低位代替取模。
21 · 字符串 02 · 2.3 String 不可变 String 不可变 + 常量池,决定了为什么拼接要用 StringBuilder。
03 · 滑动窗口 02 · 2.3 String 不可变 窗口里用 result += c 会退化成 O(n²)——每次拼都新建对象。
02 · 哈希表(自定义 key) 02 · 2.4 equals 与 hashCode 字母异位词分组把「排序后的字符串」当 key,靠的就是这两个方法的契约。
08 · 递归与回溯 03 · 3.3 栈帧与递归深度 回溯的每一层就是栈帧的一层;默认 1MB 栈只够两万层左右。
05 · 链表(递归反转) 03 · 3.3 栈帧与递归深度 5 万节点的递归反转必炸;链表长度不是你能控制的,所以写迭代。
01 · 数组与双指针 03 · 3.4 对象布局 存 100 万个 int:数组 4MB,LinkedList 48MB——差在对象头和缓存行。
18 · 二叉搜索树 11 · 11.2 为什么不用二叉树 BST 是「两叉、层数 log n」;B+ 树是「千叉、层数 3」——差别在磁盘 IO 不是比较次数。
19 · Trie 前缀树 11 · 11.6 索引失效 前缀能定位、中缀不能:这就是 like 'abc%' 走索引、'%abc' 不走的同一个道理。
07 · 二叉树(最大深度) 03 · 3.3 栈帧与递归深度 树退化成一条链时,5 万个节点的递归就在 1MB 栈的上限上跳舞。
15 · 设计题(LRU) 15 · 工程手写题(待写) LRU = 哈希表 + 双向链表,两道八股的手写合体。
12 · 图论 09 · Spring 循环依赖(待写) 三级缓存解决循环依赖,本质是图上的环检测 + 提前暴露。
12 · 图论(环检测) 12 · 12.8 死锁 死锁就是「事务等待图里出现了环」,InnoDB 的检测就是找环,找到就回滚一个。
05 · 链表(遍历) 12 · 12.3 undo 版本链 DB_ROLL_PTR 就是版本链的 next 指针,找可见版本=顺着链往回走。
06 · 栈与队列(队列语义) 08 · 8.2 阻塞队列与扩容 算法里队列是你手里的容器,线程池里它是线程之间的交接台:offer 满了阻塞、poll 空了等待。
16 · 分治(归并排序) 08 · 8.7 LongAdder 分段累加 把抢同一个变量的热点拆成多个 Cell 各记各的,最后合并——和「先分再合」是同一个思路。

A.3画图清单:哪几题必须会画

面试里用嘴说「数组加链表加红黑树」对方很难跟上,画三笔反而更快讲清。 下面这几张图建议在纸上各练三遍,练到 20 秒内画完。

图用在什么追问上必须画出的元素
HashMap 骨架图 「它的结构是什么」 一列桶数组 + 其中两个桶里挂链表 + 一个桶画成树 + 标出 16 / 0.75
一次 put 的路径图 「put 的流程说下」 算 hash → 取模定位 → 空桶放入 / 非空遍历 → 尾插 → 判断树化与扩容
扩容拆链图 「扩容怎么迁移」 一条链拆成两条,标出「原位 j」和「j + oldCap」,注明判断依据是 hash & oldCap
JDK 7 成环图 「为什么会死循环」 两个线程 + 一个变成 a→b→a 的环,圈出「get 落在这里就出不来」
画图的隐性加分项:画完主动标一下「这里是 O(1)」「这里是 O(log n)」。 面试官会立刻知道你不仅记得结构,还记得每一段的代价——这是「背过」和「想过」的分界线。

A.4追问链路:一题被追到第五层

面试的真实形态是一条向下的链,不是一张并列的表。下面这条链从一句最普通的话开始, 每一层都是上一层的自然延续——练这条链,比背八个独立问题有用得多。

层级对方问你要给出
第 1 层「你这个为什么用 HashMap?」因为要按 key 快速查——平均 O(1)。
第 2 层「为什么能 O(1)?」算 hash、用 n-1 掩码取低位定位桶,一次寻址。
第 3 层「冲突了怎么办?」桶内链表;长度到 8 且容量够 64 转红黑树,最坏降到 O(log n)。
第 4 层「元素多了会怎样?」超过容量×0.75 扩容翻倍;迁移只看 hash & oldCap 一位,原位或 +oldCap。
第 5 层「并发下能用吗?」不能。JDK 7 会成环,JDK 8 会丢数据;并发要用 ConcurrentHashMap(CAS + 锁桶头)。

JVM 这条更典型:从一个「方法跑挂了」的现象出发,五层之内会穿过栈、堆、对象布局,最后落到线上排查:

层级对方问你要给出
第 1 层「这个方法递归 10 万层怎么就挂了?」栈溢出(StackOverflowError),是调用太深,不是内存不够。
第 2 层「栈里到底存的什么?」栈帧:局部变量表、操作数栈、动态链接、返回地址。每调一次压一帧。
第 3 层「那为什么不是堆溢出?」对象在堆、调用在栈。一个报 StackOverflowError、一个报 OutOfMemoryError,处理方式完全不同。
第 4 层「那对象在堆里占多大?」头 12 字节(Mark Word 8 + 类型指针 4)+ 字段 + 按 8 对齐;new Object() 是 16。
第 5 层「线上真 OOM 了你怎么查?」先看报错文本定区(heap / Metaspace / native thread),再看 Full GC 后老年代能不能降——降不下是泄漏,加 -Xmx 没用。

同样的练法,Java 基础这条链从「一句大白话」出发,五层之内会把值传递、不可变、equals 契约全串起来:

层级对方问你要给出
第 1 层「我在方法里改了对象,外面怎么也跟着变了?」因为传的是引用的副本,副本和实参指向同一个对象,改的是对象内部。
第 2 层「那不就是引用传递吗?」不是。让形参重新指向新对象试试,外面一点不变——这正说明传的是值。
第 3 层「那 String 传进去为什么改不了?」String 本身不可变,任何「修改」都在造新对象,和传参方式无关。
第 4 层「不可变到底换来什么?」常量池能共享、hashCode 能只算一次、天然线程安全;代价是拼接要造新对象,所以有 StringBuilder。
第 5 层「那自定义对象当 HashMap 的 key 要注意什么?」equals 与 hashCode 必须一起重写、参数写成 Object,且不要拿可变字段算 hash。

索引这条链的起点是一句「这条 SQL 怎么这么慢」——它会在五层之内把 B+ 树、回表、最左前缀全部拉出来:

层级对方问你要给出
第 1 层「这条 SQL 为什么慢?」先看执行计划:type 是不是 ALL、rows 有多大、Extra 有没有 Using filesort。
第 2 层「为什么没走索引?」列上做运算 / 函数、隐式类型转换、左模糊 like,或者 or 另一边没索引——本质都是「列的值在比较前被改了」。
第 3 层「加了索引为什么还是慢?」key_len 看用到了联合索引的第几列;再看 Extra 有没有 Using index(没覆盖就要回表)。
第 4 层「回表为什么贵?」二级索引叶子只有「索引列 + 主键」,取整行要按主键再走一次聚簇索引,是随机 IO。
第 5 层「那为什么不用二叉树省得回表?」二叉树 100 万节点 20 层就是 20 次 IO;B+ 树一页 1638 个指针、3 层撑到亿级,IO 只有 3 次。

事务这条链从「我明明提交了,他怎么还读不到」出发,五层之内会走到 ReadView 的四个数字上:

层级对方问你要给出
第 1 层「为什么我提交了,他那边还读不到?」他开的是可重复读,读的是快照,不是最新数据——差的是 ReadView 的时刻。
第 2 层「那这个快照存在哪?」没有副本。只有四个数字:m_ids、min_trx_id、max_trx_id、creator_trx_id。
第 3 层「那它怎么知道该看哪个版本?」顺 DB_ROLL_PTR 沿 undo 版本链往回走,拿每个版本的 DB_TRX_ID 和 ReadView 比,第一个可见的就是。
第 4 层「那 RR 和 RC 差在哪?」只差 ReadView 建的频率:RC 每条 select 一次,RR 整个事务复用一次。
第 5 层「那同一个事务里 update 用的是旧值还是新值?」新值。update 是当前读,不走 MVCC——所以 set v = v + 1 安全,先查后写不安全。

并发这条链从「线上接口全卡住了」出发,五层之内会落到线程池的三个数字上:

层级对方问你要给出
第 1 层「线上接口全卡住了,你怎么查?」先 jstack 看线程状态分布:全 WAITING 是等队列,全 BLOCKED 是抢锁,全 RUNNABLE 停在 socket 是下游超时。
第 2 层「怎么确定是线程池打满?」看三个数一起涨:活跃线程数 = max、队列长度持续上涨、completedTaskCount 不涨。
第 3 层「为什么会打满?」任务里有阻塞调用(慢 SQL / 远程接口)占了线程,或者线程数本来就配少了。前者居多。
第 4 层「那多加线程不就行了?」16 核实测:16 → 32 线程几乎不变(59→56ms),32 → 64 反而变慢(56→65ms),因为上下文切换。
第 5 层「那到底怎么配?」队列必须有界(否则 max 不生效、内存涨到 OOM),handler 用 CallerRuns 形成背压,配好线程名,再按实测调参数。
怎么用这六张表:找一个同学(或对着录音)从第 1 层开始问,你连续答到第 5 层不卡壳,这一条就算过了。 卡在第几层,就回去读对应那一节——这比从头再读一遍整章高效得多。

A.5卡住了去哪查

按「可信度」排序,而不是按「搜索是否方便」排序。前两个是权威源,后两个是辅助理解。

来源性质什么时候用
JDK 源码
java.util.HashMap
权威 所有「具体数值、阈值、流程」的问题。注意看注释——树化阈值 8 的泊松分布说明就写在类注释里,比任何二手资料都准。
JEP / 官方文档 权威 「为什么这么设计」的问题,比如某版本为什么改实现。
JavaGuide / 小林 coding 二手,质量高 建立整体框架、找图。看到具体数字仍要回源码核一遍。
博客 / 面试题合集 三手 只用来「看别人怎么组织表达」,不要信其中的数字和结论——版本漂移最严重的就是这类内容。
一个真实存在的坑:网上大量文章写「JDK 8 之后 HashMap 就线程安全了」——这是错的。 JDK 8 只是修掉了「成环」,数据覆盖依然存在。 判断一条八股是否过期,最可靠的办法是回源码看那段逻辑还在不在,而不是看文章的发布时间。
一句话记忆(附录):复习看 A.1,缝合看 A.2,表达看 A.3 和 A.4,求证看源码—— 四件事不要混着做,混着做就是「看了很多,什么也没留下」。