# 02.面试八股

# 🟡 题1:HashMap 的树化与退化

题目
HashMap 中链表转红黑树的阈值是 8,红黑树退化成链表的阈值是 6,为什么不是 7?
如果 hashCode 分布非常均匀,还会发生树化吗?

  • 8和6之间由7作为缓冲,防止因频繁树化,退化造成性能抖动

  • 树化除了链表长度 ≥ 8 外,还要数组长度 ≥ 64(否则先扩容),hashCode 分布非常均匀时,链表长度几乎不会超过 8,不会树化

# 🔴 题2:String 的“不可变性”真的绝对吗?

题目
以下代码会输出什么?为什么?

String s1 = "hello";
String s2 = s1;
try {
 Field field = String.class.getDeclaredField("value");
 field.setAccessible(true);
 char[] value = (char[]) field.get(s1);
 value[0] = 'H';
} catch (Exception e) {}
System.out.println(s1);
System.out.println(s2);
1
2
3
4
5
6
7
8
9
10

都输出Hello,String一般是不可变的,但是可以通过反射,修改char数组中的值

s2输出Hello是因为,他们都指向相同的堆内存引用

# 🟢 题3:异常处理中的 finally 与 return

题目
以下代码输出什么?

public static int test() {
    int x = 1;
    try {
        return x;
    } finally {
        x = 2;
    }
}
System.out.println(test());
1
2
3
4
5
6
7
8
9

1,已经返回了,最后给x赋值影响不了什么

# 🟡 题4:对象一定在堆上分配吗?

题目
哪些情况下对象不直接在堆上分配?说出 2 种以上。

  1. 栈上分配(标量替换)

    • 逃逸分析 + JIT 确定对象不逃逸 → 拆散为变量存在栈帧
逃逸程度 描述 分配策略
不逃逸 (No Escape) 对象仅在方法内部使用,不会被外部引用。 栈上分配 或 标量替换
方法逃逸 (Method Escape) 对象被返回,或被传递给其他方法,但未跨线程。 通常在堆上分配(可能尝试优化)
线程逃逸 (Thread Escape) 对象被赋值给静态变量,或被其他线程访问。 必须在堆上分配
public void testNoEscape() {
    // 对象仅在方法内使用,未返回,未赋值给外部变量
    User user = new User("Alice", 18); 
    System.out.println(user.getName());
}
1
2
3
4
5

# 🟢 题5:Full GC 频繁的排查思路

题目
线上系统频繁 Full GC,你作为实习生接到任务排查,给出完整排查步骤

  1. jstat -gcutil <pid> 1s 看老年代、Eden、GC 次数

  2. jmap -histo <pid> | head -20 看大对象、内存泄漏嫌疑

  3. 可疑时 jmap -dump:live,format=b,file=heap.hprof <pid>

  4. 用 MAT / JProfiler 分析 dump(重点看 GC Roots、大对象引用链)

故障类型 典型特征 常见原因 解决方案
内存泄漏 Full GC 后老年代占用率不下降,呈阶梯状上涨 1. 静态集合(static Map/List)无限添加不清理
2. ThreadLocalremove
3. 未关闭的资源(IO流、DB连接)
4. 监听器/回调未注销
1. 使用 MAT 定位泄漏对象
2. 改用 WeakHashMap 或带过期策略的缓存(Caffeine)
3. 规范资源关闭(try-with-resources)
大对象过早晋升 老年代占用高,但无泄漏,Full GC 后能回收一部分 1. 代码中一次性加载大量数据(如全表查询)
2. 超大字符串或 byte 数组
3. 频繁的大对象分配直接进入老年代
1. 优化 SQL,使用分页查询
2. 检查代码中的大对象创建
3. 调整 -XX:PretenureSizeThreshold(仅限 Serial/ParNew)
元空间不足 GC 日志显示 Metadata GC Threshold,Metaspace 持续增长 1. 大量使用 CGLIB/Dynamic Proxy
2. 频繁的热部署或动态加载类
3. JSON 序列化框架生成大量类
1. 适当调大 -XX:MaxMetaspaceSize
2. 优化代码减少动态类生成
堆配置不合理 频繁 Full GC,但每次回收效果尚可,只是空间太小 1. 新生代(Young Gen)太小,对象过早进入老年代
2. Survivor 区太小,对象提前晋升
1. 调整 -Xmn(新生代大小)
2. 调整 -XX:SurvivorRatio
3. 调大堆内存 -Xmx
显式 GC GC 日志明确记录 System.gc() 1. 代码中调用了 System.gc()
2. 使用了 RMI 或 NIO 的 DirectByteBuffer 自动清理机制
1. 搜索代码并删除
2. 启动参数添加 -XX:+DisableExplicitGC

# 🔴 题6:类加载的“死锁”场景

题目
两个自定义类加载器相互引用对方加载的类,会不会死锁?为什么?

“通常情况下不会死锁

JVM 机制保护:JVM 在类加载和链接阶段有内置的状态检查机制。如果检测到类的继承链或依赖链存在循环(例如 A 依赖 B,B 依赖 A),JVM 不会让线程无限阻塞,而是会抛出 ClassCircularityError

# 🟡 题7:synchronized 的锁升级过程

题目
synchronized 在 JDK 1.6 后做了哪些优化?无锁 → 偏向锁 → 轻量级锁 → 重量级锁分别在什么条件下发生?

在JDK1.6有了锁升级机制,会根据系统中的需要对锁进行升级

当最初创建时为无锁状态,对象未被任何线程访问,当有一个线程访问后进入偏向锁的状态,当有第二个线程进行访问时进入轻量级锁状态,第二个线程如果获取不到锁,不会进入阻塞状态而是进行CAS+自旋(自己调整循环次数,占用CPU,不切换上下文),当第二个线程一直获取不到锁或者系统并发比较高时,进入重量级锁状态,获取不到锁的线程会被放到队列中,等待操作系统来唤醒,涉及到上下文的切换,开销比较大

锁状态 触发条件 核心机制 性能开销
偏向锁 只有1个线程反复访问 记录线程ID,无同步操作 极低 (几乎无开销)
轻量级锁 有少量线程竞争,但时间短 CAS + 自旋 (占用CPU但不阻塞) 较小 (避免内核切换)
重量级锁 激烈竞争,自旋失败 操作系统互斥量,线程阻塞 极大 (涉及内核切换)

# 🔴题8:线程池中的“工作窃取”

题目
ForkJoinPool 的核心机制是什么?和普通 ThreadPoolExecutor 最大的区别在哪里?

  • 核心机制:工作窃取

  • 区别:

    • ThreadPoolExecutor:一个全局的共享队列

    • ForkJoinPool:每个线程有自己的双端队列,空闲线程从其他线程队尾“偷”任务

  • 适用场景:分治任务(递归拆分 + 合并结果)

# 🟢 题9:volatile + 双重检查锁的坑

题目
以下 DCL 单例有什么问题?

if (instance == null) {
     synchronized (Singleton.class) {
     if (instance == null) {
         instance = new Singleton();
         }
     }
}
1
2
3
4
5
6
7
  • 指令重排序问题new Singleton() 不是原子操作

    1. 分配内存

    2. 初始化对象

    3. instance 指向内存 ← 2 和 3 可能重排

  • 其他线程可能拿到未初始化完成的对象

  • 修复:private static volatile Singleton instance

# 🟡 题10:一个 SQL 不走索引的真实案例

题目
表 user(name, age),联合索引 (name, age),以下 SQL 是否走索引?为什么?

sql

SELECT * FROM user WHERE age = 20 AND name LIKE '%张%';

  • 不会走联合索引

  • 原因:

    • LIKE '%张%' 是前缀模糊匹配,无法使用索引

    • 即使条件顺序调换也没用

  • 优化方向:name LIKE '张%'(前缀匹配可用索引)或走全文索引

# 🔴 题11:MVCC 与 Read View

题目
在 RR 隔离级别下,一个事务开启后一直不提交,另一个事务 insert 一条新数据,第一个事务能否读到?为什么?

MVCC 中 Read View 在第一个 SELECT 时创建(不是事务开始时)

场景 操作顺序 结果 原因
场景一 事务1查询 → 事务2插入提交 → 事务1再查询 查不到 事务1在事务2插入之前就创建了快照,快照里不包含新数据。
场景二 事务2插入提交 → 事务1首次查询 能查到 事务1在事务2提交之后才创建快照,新数据对快照可见。

# 🟢 题12:锁的“自查询”场景

题目
以下 SQL 在 RR 隔离级别下会锁哪些行?

SELECT * FROM orders WHERE id > 10 FOR UPDATE;
1

表中 id 为主键,值:5, 8, 11, 15, 20

  • 锁定的行:11, 15, 20

  • 同时锁定 id 10 到正无穷的间隙(包括 id=20 之后的无限)

  • 常见错误:认为会锁 5, 8(不会,因为 id ≤ 10 的行不在范围)

最近更新: 9/19/2026, 1:27:08 PM
02.面试八股

编程NOTE   |