Java 面试官最喜欢问的 30 个底层问题(深度解析版)
很多 Java 面试都有一个固定套路:
一面:项目经验
二面:框架理解
三面:底层原理
当面试进入第三轮时,问题往往会变成:
HashMap 为什么链表长度是 8 才转红黑树?
CAS 为什么会出现 ABA 问题?
Spring 为什么要设计三级缓存?
这些问题的本质不是考你记忆,而是考 你对 Java 底层机制的理解程度。
本文整理 Java 面试官最喜欢问的 30 个底层问题 + 深度解析。
一、Java 基础
1 HashMap 底层数据结构是什么?
在 JDK1.8 之后,HashMap 的结构是:
数组 + 链表 + 红黑树
结构示意:
table(数组)
↓
bucket
↓
Node → Node → Node
↓
TreeNode(红黑树)
工作流程
当执行 put(key,value) 时:
第一步:计算 hash
int hash = key.hashCode() ^ (hash >>> 16);
目的:
减少哈希冲突
让高位参与运算
第二步:定位数组下标
index = (n - 1) & hash
第三步:
桶为空 → 直接插入
桶不为空 → 产生哈希冲突
冲突后:
链表插入
当链表长度 ≥ 8 时:
链表 → 红黑树
为什么是 8?
原因是 泊松分布概率计算。
当负载因子是 0.75 时:
链表长度 >= 8 的概率极低
因此:
树化阈值 = 8
2 HashMap 为什么线程不安全?
在 多线程环境下,HashMap 可能出现严重问题。
典型场景:
两个线程同时进行扩容 resize
扩容流程:
旧数组 → 新数组
数据迁移:
Node → 重新计算 index
在 JDK1.7 中:
链表头插法
多线程下可能形成:
链表环
最终导致:
CPU 100%
死循环
JDK1.8 改成:
尾插法
虽然降低风险,但仍然 不是线程安全。
并发场景应该使用:
ConcurrentHashMap
3 equals 和 hashCode 为什么必须一起重写?
HashMap 查找流程:
hashCode → 定位桶
equals → 判断 key 是否相等
假设:
两个对象 equals=true
但 hashCode 不相等
结果:
进入不同桶
导致:
HashMap 认为是两个 key
示例:
Map<Person,String> map = new HashMap<>();
map.put(new Person("Tom"), "A");
map.get(new Person("Tom"));
如果没有重写 hashCode:
返回 null
因为进入不同 bucket。
4 String 为什么不可变?
String 的源码:
private final char[] value;
关键点:
final
意味着:
一旦创建无法修改
好处:
1 线程安全
多个线程共享 String 不需要同步。
2 字符串常量池
例如:
String a = "java";
String b = "java";
实际上:
a 和 b 指向同一个对象
节省内存。
3 安全性
很多系统关键参数是 String:
数据库连接
文件路径
网络地址
如果可变:
可能被篡改
二、JVM
5 JVM 内存结构是什么?
JVM 运行时数据区:
线程共享区域
↓
堆
方法区
线程私有区域
↓
虚拟机栈
本地方法栈
程序计数器
结构:
JVM
├─ Heap
├─ Method Area
├─ Java Stack
├─ Native Stack
└─ PC Register
堆
存储:
对象
数组
结构:
新生代
老年代
新生代:
Eden
S0
S1
6 什么是 GC Root?
GC 使用:
可达性分析算法
判断对象是否存活。
从 GC Root 出发:
GC Root → 对象引用 → 对象
如果对象:
不可达
就会被回收。
常见 GC Root:
栈引用对象
静态变量
常量池
JNI引用
7 JVM 类加载过程
类加载五个阶段:
加载
验证
准备
解析
初始化
加载
读取 .class 文件。
验证
检查:
字节码合法性
准备
为静态变量分配内存。
解析
符号引用 → 直接引用。
初始化
执行:
static 代码块
三、并发编程
8 volatile 的作用
volatile 保证:
可见性
禁止指令重排
可见性
CPU 有:
寄存器
缓存
主内存
线程修改变量后:
volatile
↓
立即刷新到主内存
其他线程可以看到。
9 synchronized 原理
synchronized 底层依赖:
对象头 + Monitor
对象头中有:
Mark Word
用于记录:
锁状态
线程ID
锁升级流程:
无锁
↓
偏向锁
↓
轻量级锁
↓
重量级锁
这样可以减少:
线程阻塞
提高性能。
10 CAS 原理
CAS:
Compare And Swap
执行流程:
比较内存值 V
期望值 A
如果:
V == A
则:
更新为 B
否则:
重试
Java 中使用:
Unsafe 类
调用 CPU 指令。
四、MySQL
11 为什么 MySQL 使用 B+树?
B+树特点:
所有数据都在叶子节点
结构:
非叶子节点:索引
叶子节点:数据
优点:
1 减少磁盘 IO
树高度通常:
3~4 层
即可存储 千万级数据。
2 范围查询快
叶子节点:
双向链表
支持:
range scan
五、Redis
12 Redis 为什么快?
Redis 快的原因:
1 内存数据库
数据在:
RAM
没有磁盘 IO。
2 单线程模型
避免:
线程上下文切换
锁竞争
3 IO 多路复用
使用:
epoll
同时处理大量连接。
六、Spring
13 Spring 循环依赖为什么要三级缓存?
如果:
A 依赖 B
B 依赖 A
会产生:
循环依赖
Spring 使用三级缓存:
一级缓存:singletonObjects
二级缓存:earlySingletonObjects
三级缓存:singletonFactories
流程:
创建A
↓
提前暴露A
↓
B引用A
↓
完成初始化
这样解决循环依赖。
三、并发编程(续)
14 什么是 Java 内存模型(JMM)?
Java 内存模型(Java Memory Model)主要解决 多线程之间共享变量的可见性问题。
JMM 规定:
线程不能直接操作主内存
必须通过工作内存
结构:
主内存
↑↓
线程A工作内存
线程B工作内存
线程执行流程:
主内存 → 线程工作内存 → 修改变量 → 刷新回主内存
JMM 主要解决三个问题:
| 问题 | 说明 |
|---|---|
| 原子性 | 操作不可被中断 |
| 可见性 | 一个线程修改其他线程能看到 |
| 有序性 | 防止指令重排 |
面试追问:
volatile 如何保证可见性?
15 ThreadLocal 原理是什么?
ThreadLocal 用于:
线程本地变量
每个线程都有自己的变量副本。
底层结构:
Thread
↓
ThreadLocalMap
↓
Entry(ThreadLocal, value)
源码结构:
ThreadLocalMap map = Thread.threadLocals;
特点:
key:ThreadLocal对象
value:实际数据
优点:
避免线程安全问题
常见应用:
Spring 事务
MyBatis SqlSession
用户上下文
16 ThreadLocal 为什么会内存泄漏?
ThreadLocalMap 的 Entry:
static class Entry extends WeakReference<ThreadLocal<?>>
key 是:
弱引用
value 是:
强引用
如果:
ThreadLocal对象被回收
则:
key = null
value 仍然存在
只要线程不结束:
value 就不会释放
解决方法:
threadLocal.remove();
17 线程池的执行流程是什么?
线程池核心类:
ThreadPoolExecutor
任务执行流程:
提交任务
↓
核心线程池
↓
阻塞队列
↓
最大线程池
↓
拒绝策略
流程图:
任务
↓
corePoolSize
↓
workQueue
↓
maximumPoolSize
↓
reject
核心参数:
corePoolSize
maximumPoolSize
workQueue
keepAliveTime
18 线程池为什么不建议使用 Executors?
因为 Executors 创建的线程池:
可能导致 OOM
例如:
newFixedThreadPool
队列:LinkedBlockingQueue
特点:
无界队列
如果任务过多:
内存爆炸
正确方式:
new ThreadPoolExecutor(...)
四、JVM 进阶
19 JVM 对象创建过程
创建对象步骤:
1 类加载检查
2 分配内存
3 初始化零值
4 设置对象头
5 执行构造方法
示例:
new Object();
JVM执行:
new
dup
invokespecial
20 JVM 对象在内存中的结构
对象结构:
对象头
实例数据
对齐填充
对象头包括:
Mark Word
Class Pointer
Mark Word 用于:
锁信息
GC信息
HashCode
21 什么是逃逸分析?
逃逸分析是 JVM 优化技术。
JVM 判断:
对象是否逃出方法
如果没有逃逸:
可以优化为:
栈上分配
或者:
标量替换
例如:
new User();
如果只在方法内使用:
不会进入堆
五、MySQL
22 InnoDB 和 MyISAM 区别
| 特性 | InnoDB | MyISAM |
|---|---|---|
| 事务 | 支持 | 不支持 |
| 锁 | 行锁 | 表锁 |
| 外键 | 支持 | 不支持 |
| MVCC | 支持 | 不支持 |
企业一般使用:
InnoDB
23 什么是回表?
例如:
select name from user where age=20
如果:
age 有索引
name 没有
执行流程:
索引树找到ID
↓
回主键索引
↓
获取完整数据
这个过程叫:
回表
解决方案:
覆盖索引
24 什么是覆盖索引?
如果查询字段:
全部在索引中
就不需要回表。
例如:
index(age,name)
查询:
select age,name
执行:
直接从索引返回
速度更快。
25 什么是索引下推(ICP)?
Index Condition Pushdown
优化:
在存储引擎层过滤数据
减少:
回表次数
例如:
where age=20 and name like 'A%'
六、Redis
26 Redis 为什么使用单线程?
很多人误以为:
Redis 单线程 = 性能差
其实不是。
原因:
1 Redis 是内存数据库
速度极快
2 避免锁竞争
多线程需要:
加锁
上下文切换
反而慢。
27 Redis 持久化方式有哪些?
Redis 持久化有两种:
RDB
定时快照
优点:
恢复快
缺点:
可能丢数据
AOF
追加日志
优点:
数据安全
缺点:
文件大
28 Redis 为什么会有缓存击穿?
缓存击穿:
热点 key 失效
大量请求:
直接打到数据库
解决方案:
互斥锁
永不过期
逻辑过期
七、Spring
29 Spring Bean 生命周期
Bean 生命周期:
实例化
↓
依赖注入
↓
BeanPostProcessor
↓
初始化
↓
使用
↓
销毁
关键扩展点:
BeanPostProcessor
30 Spring Boot 自动配置原理
Spring Boot 自动配置核心:
@EnableAutoConfiguration
加载:
META-INF/spring.factories
流程:
启动
↓
扫描配置类
↓
条件判断
↓
自动装配
例如:
DataSourceAutoConfiguration
自动创建:
数据源
面试官真正想考什么
这些问题其实考察三个能力:
基础理解
源码能力
系统思维
如果能回答到:
原理
源码
设计思想
基本可以拿下 高级 Java 面试。
结语
Java 底层知识核心其实就 六大模块:
Java基础
JVM
并发
数据库
缓存
Spring
如果这些内容理解透彻:
90% 的 Java 面试都可以轻松应对。
更多推荐




所有评论(0)