很多 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 面试都可以轻松应对。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐