30 题
6 个专题
每题带出处
用这张表的方式:先遮住答案自答一遍,答不出就看「追问」——面试拉开差距的从来不是第一问,
是第二问。想深挖的点,用题后的地图链接点进去看节点。
一、JVM
-
堆为什么要分代?
弱分代假说:绝大多数对象朝生夕灭,少数会长期存活。分开管,才能对新生代用复制算法(便宜),
对老年代用标记整理(省空间)。
追问:G1 还分代吗?——逻辑分代,物理上是 Region,不再连续
-
什么对象能当 GC Roots?
栈帧里的局部变量、静态变量、常量引用、JNI 引用、活跃线程、同步锁对象。
追问:为什么不用「引用计数」——循环引用无法回收
-
JDK 8 为什么用元空间替换永久代?
永久代大小要手工调、容易
PermGen space OOM,且与堆共用限额;
元空间用本地内存,默认不设上限,类卸载还能回收。
追问:字符串常量池在哪——JDK 7 起已在堆里
-
G1 和 CMS 的核心差别?
G1 可设停顿目标(
MaxGCPauseMillis)、按 Region 收集、整体标记整理不会碎片化;
CMS 用标记清除,会碎片,可能 concurrent mode failure 退化成 Serial Old 全停顿。
追问:G1 的回收流程——Young GC / 并发标记 / Mixed GC / Full GC
-
线上 Full GC 频繁怎么查?
jstat -gcutil 看 FGC/FGCT 增速 → GC.class_histogram 找实例暴涨的类 →
必要时 dump 对比两次。
追问:两个 dump 怎么比——MAT 的 Histogram / Dominator Tree 对比
-
TLAB 是干什么的?
Thread Local Allocation Buffer,每个线程在 Eden 里分一小块独享,分配对象只用移动指针,
不用抢同一把锁。
追问:TLAB 满了怎么办——申请新 TLAB,或直接在 Eden 共享区 CAS 分配
二、Spring
-
Bean 的生命周期顺序?
实例化 → 属性填充 → Aware → postProcessBeforeInitialization →
@PostConstruct →
afterPropertiesSet → init-method → postProcessAfterInitialization(此处生成 AOP 代理)。
追问:代理哪一步生成——最后一个 BPP 回调
-
三级缓存解决什么问题?
循环依赖时提前暴露一个「半成品引用」。第三级
singletonFactories 存的是工厂,
能在需要时生成 AOP 代理。
追问:第二级为什么存在——避免同一个 Bean 多次调用工厂生成不同代理
-
为什么构造器注入解决不了循环依赖?
构造器阶段就需要完整的依赖对象,无法先给半成品,直接抛
BeanCurrentlyInCreationException。
追问:Spring Boot 2.6 起默认怎么处理——禁止循环依赖
-
@Transactional 什么情况下失效?
自调用、非 public、final 方法、对象不在容器里、异常被吞、跨线程(事务绑定在 ThreadLocal)。
追问:同类自调用怎么破——拆 Bean / AopContext / 注入自身代理
-
BeanFactory 和 FactoryBean 的区别?
BeanFactory 是容器本身;FactoryBean 是容器里的一个「造对象的工厂」,
getBean("&xxx") 才拿到工厂本身。
追问:典型用途——MyBatis 的 MapperFactoryBean
-
Spring Boot 自动装配靠什么生效?
@Import(AutoConfigurationImportSelector) 读
META-INF/spring/…AutoConfiguration.imports(2.7 前是 spring.factories),
再按 @ConditionalOn* 过滤。
追问:3.0 升级后 starter 静默失效——旧文件路径不再被读
三、微服务
-
Eureka 和 Nacos 的核心差异?
Eureka 是 AP、纯客户端心跳、只做服务发现;Nacos 支持 AP/CP 切换(临时实例 AP,持久实例走 Raft CP),
还带配置中心。
追问:为什么注册中心多选 AP——分区时「能发现旧实例」好过「完全不可用」
-
ephemeral=false 意味着什么?
实例变成持久实例,不靠客户端心跳,由服务端主动探活,数据走 CP 一致性,实例下线需显式注销。
追问:什么场景该用——IP 固定的中间件、非 Java 客户端
-
网关为什么基于 WebFlux?
Netty 事件循环,少量线程扛大量连接。代价是一个阻塞调用就能卡住整条事件循环上的所有请求。
追问:必须调阻塞接口怎么办——切到 boundedElastic 调度器
-
熔断和降级的区别?
熔断是「发现下游不行了,短时间内不再调用」;降级是「调用失败时返回兜底结果」。
熔断是手段,降级是策略。
追问:三种状态——Closed / Open / Half-Open
-
配置中心动态刷新怎么实现的?
@RefreshScope 的 Bean 被包在一个 Scope 代理里,刷新事件触发后销毁缓存实例,
下次调用重建。
追问:为什么静态字段刷不到——它不经过代理
四、并发
-
HashMap 什么时候树化?
链表长度 ≥ 8 且 数组容量 ≥ 64;容量不足时优先扩容。退化阈值是 6。
追问:为什么两个阈值不一样——留出缓冲,避免反复树化/退化
-
ConcurrentHashMap 怎么保证线程安全?
JDK 8:桶为空用 CAS 插入,桶非空
synchronized 锁桶首节点;计数用
baseCount + CounterCell 分散竞争。
追问:为什么不用 JDK 7 的分段锁——粒度太粗、内存浪费
-
线程池核心参数与执行顺序?
核心线程 → 队列 → 最大线程 → 拒绝策略。
Executors 的几个工厂方法要么队列无界
(FixedThreadPool),要么线程无界(CachedThreadPool),都可能 OOM。
追问:四种拒绝策略——Abort / CallerRuns / Discard / DiscardOldest
-
ThreadLocal 为什么会内存泄漏?
Entry 的 key 是
WeakReference<ThreadLocal>,value 是强引用。
ThreadLocal 被回收后 key 变 null,value 却挂在 Thread 的 Map 上;线程池复用线程时永不释放。
追问:正确用法——finally 里 remove()
-
volatile 保证什么、不保证什么?
保证可见性与禁止指令重排(内存屏障),不保证原子性;i++ 依然会丢更新。
追问:DCL 单例为什么要加 volatile——防止「引用可见但对象未初始化完」
-
synchronized 锁升级过程?
无锁 → 偏向锁 → 轻量级锁(CAS 自旋)→ 重量级锁(内核互斥量)。
追问:JDK 15 起偏向锁默认关闭,为什么——撤销成本高、现代场景收益低
五、集合与 Stream
-
ArrayList 和 LinkedList 该怎么选?
绝大多数情况选 ArrayList。LinkedList 的「插入 O(1)」前提是你已经拿到了节点,
而按索引定位仍是 O(n),且节点分散导致缓存命中率差。
追问:ArrayList 扩容——1.5 倍,
Arrays.copyOf
-
并行流有什么坑?
默认共用公共
ForkJoinPool(核数-1 个线程),一段慢任务会拖累全应用;
且 forEach 里写非线程安全集合会丢数据。
追问:什么时候值得用——纯 CPU 计算、数据量大、无共享状态
-
为什么说 Stream 不是「更快的 for」?
它买的是可读性与可组合性。简单遍历用 for 更快,Stream 有装箱与管道开销。
追问:
mapToInt 为什么存在——避免装箱
六、IO 与 MyBatis
-
BIO / NIO / AIO 的区别?
BIO 一连接一线程;NIO 用 Selector 多路复用,一个线程管多连接;
AIO 是内核完成读写后回调(Linux 上支持不如前两者成熟)。
追问:Netty 属于哪种——NIO(epoll)
-
零拷贝是什么?
省掉「内核缓冲区 ↔ 用户缓冲区」的来回拷贝。
sendfile 让数据不出内核;
mmap 把文件映射进用户空间。
追问:Kafka 用哪个——sendfile + 页缓存
-
MyBatis 的
#{} 和 ${} 区别?
#{} 走 PreparedStatement 占位符,防注入;
${} 是字符串直接拼接,只能用于表名/排序字段这类无法参数化的位置,且必须白名单校验。
追问:一级/二级缓存——SqlSession 级 / Mapper 级,二级默认关
七、怎么用这张表补短板
- 答不上来的题,先点对应的地图节点(比读 4000 字长文快得多)。
- 「追问」答不出来但第一问答得出来,说明你背过结论、没看过实现——面试官一追问就露底。
- 每题都能说出一个你真实遇到过的现象,比背十个定义有用。
配套地图
题目涉及的机制都在交互地图里,点节点看「是什么 / 为什么 / 常见误解 / 源码位置」。
回复 地图
回复 目录
回复 面试 再取一次