2026最全 Java 面试题精选(附答案):Spring全家桶高频考点整理
本文是 Java 系列教程,从基础到进阶,附完整代码示例,收藏备用!
一、前言
Java 作为企业级开发的主流语言,面试考察点覆盖面广、深度大。本文整理了 2026 年最新高频面试题,涵盖 Java 基础、集合、并发、JVM、Spring 全家桶等核心知识点,每道题都附带详细答案和代码示例。
适合人群:
- 准备 Java 后端面试的开发者
- 需要系统复习 Java 知识点的工程师
- 想了解最新面试趋势的技术人员
二、Java 基础篇
2.1 HashMap 底层原理
问题: 讲讲 HashMap 的底层实现?JDK 1.8 做了哪些优化?
答案:
HashMap 基于数组 + 链表 + 红黑树实现:
// 核心数据结构
transient Node<K,V>[] table; // 哈希桶数组
transient int size; // 元素数量
int threshold; // 扩容阈值 = 容量 * 负载因子
final float loadFactor; // 默认 0.75
// 链表节点
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; // 指向下一个节点
}
JDK 1.8 优化:
- 链表转红黑树:当链表长度 ≥ 8 且数组长度 ≥ 64 时,链表转为红黑树,查询复杂度从 O(n) 降到 O(log n)
- 尾插法:插入时使用尾插法替代头插法,避免并发扩容时的死循环
- 扩容优化:扩容时不需要重新计算 hash,只需判断 hash 新增的高位是 0 还是 1
// 扩容时的 rehash 优化
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
// 容量翻倍
int newCap = oldCap << 1;
// 迁移数据时,只需看 hash 新增的高位
// (e.hash & oldCap) == 0 留在原位置,否则移到原位置 + oldCap
}
2.2 ConcurrentHashMap 线程安全实现
问题: ConcurrentHashMap 如何保证线程安全?1.7 和 1.8 有什么区别?
答案:
JDK 1.7:分段锁(Segment)
// 1.7 结构:Segment[] + HashEntry[]
final Segment<K,V>[] segments;
static final class Segment<K,V> extends ReentrantLock {
transient volatile HashEntry<K,V>[] table;
// 每个 Segment 独立加锁
}
JDK 1.8:CAS + synchronized
// 1.8 结构:Node[] + 链表/红黑树
// 锁粒度细化到每个桶(链表头节点)
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 1. 数组未初始化,CAS 初始化
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 2. 目标桶为空,CAS 插入
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
break;
}
// 3. 正在扩容,协助扩容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
// 4. 桶非空,synchronized 锁定头节点
else {
synchronized (f) {
if (tabAt(tab, i) == f) {
// 链表操作或树操作
}
}
}
}
}
1.8 优势:
- 锁粒度更细(桶级别 vs Segment 级别)
- 使用 CAS + synchronized 替代 ReentrantLock,性能更好
- 支持扩容时并发协助迁移
2.3 volatile 关键字
问题: volatile 的作用是什么?如何保证可见性和有序性?
答案:
public class VolatileDemo {
// volatile 保证可见性和禁止指令重排序
private volatile boolean flag = false;
public void writer() {
flag = true; // 写 volatile 变量
}
public void reader() {
while (!flag) {
// 等待
}
System.out.println("看到了 flag 的变化");
}
}
实现原理:
- 可见性:写 volatile 变量会立即刷新到主内存,读 volatile 变量会从主内存刷新
- 有序性:插入内存屏障(Memory Barrier)禁止指令重排序
内存屏障:
- StoreStore 屏障:禁止普通写与 volatile 写重排序
- StoreLoad 屏障:禁止 volatile 写与后续 volatile 读/写重排序
- LoadLoad 屏障:禁止 volatile 读与普通读重排序
- LoadStore 屏障:禁止 volatile 读与普通写重排序
三、Java 并发篇
3.1 synchronized 锁升级过程
问题: synchronized 的锁升级过程是怎样的?
答案:
synchronized 锁有 4 种状态,只能升级不能降级:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
1. 偏向锁(Biased Locking)
// 场景:只有一个线程访问同步块
// 原理:在对象头 Mark Word 中记录线程 ID,下次同一线程访问无需 CAS
// 开启偏向锁(默认开启)
-XX:+UseBiasedLocking
-XX:BiasedLockingStartupDelay=0
2. 轻量级锁(Lightweight Locking)
// 场景:多个线程交替访问(无竞争)
// 原理:CAS 自旋尝试获取锁,避免线程阻塞
// 自旋次数默认 10 次,可通过参数调整
-XX:PreBlockSpin=10
3. 重量级锁(Heavyweight Locking)
// 场景:多个线程同时竞争
// 原理:线程阻塞,等待操作系统调度
对象头 Mark Word 结构:
| 锁状态 | 存储内容 |
|---------|-------------------------------------|
| 无锁 | 对象哈希码 + 分代年龄 + 0 01 |
| 偏向锁 | 线程 ID + Epoch + 分代年龄 + 1 01 |
| 轻量级锁 | 指向栈中锁记录的指针 00 |
| 重量级锁 | 指向互斥量(Monitor)的指针 10 |
| GC 标记 | 空 11 |
3.2 AQS 原理
问题: AbstractQueuedSynchronizer(AQS)的原理是什么?
答案:
AQS 是 JUC 包的核心框架,ReentrantLock、CountDownLatch、Semaphore 都基于它实现。
核心结构:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer {
// 同步状态,volatile 保证可见性
private volatile int state;
// 等待队列头节点
private transient volatile Node head;
// 等待队列尾节点
private transient volatile Node tail;
// 独占线程
private transient Thread exclusiveOwnerThread;
}
// 队列节点
static final class Node {
volatile int waitStatus; // 节点状态
volatile Node prev; // 前驱节点
volatile Node next; // 后继节点
volatile Thread thread; // 绑定的线程
}
获取锁流程:
// ReentrantLock 的 lock 方法
final void lock() {
// 1. 尝试 CAS 获取锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 获取失败,进入队列
}
// AQS 的 acquire 方法
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 子类实现
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
3.3 线程池参数与拒绝策略
问题: 线程池的核心参数有哪些?拒绝策略有哪些?
答案:
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 非核心线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
任务执行流程:
- 当前运行线程 < corePoolSize:创建新线程执行任务
- 当前运行线程 ≥ corePoolSize:任务进入队列
- 队列已满且线程 < maximumPoolSize:创建非核心线程
- 队列已满且线程 ≥ maximumPoolSize:执行拒绝策略
拒绝策略:
// 1. AbortPolicy(默认):直接抛出异常
new ThreadPoolExecutor.AbortPolicy();
// 2. CallerRunsPolicy:由调用线程执行任务
new ThreadPoolExecutor.CallerRunsPolicy();
// 3. DiscardPolicy:静默丢弃任务
new ThreadPoolExecutor.DiscardPolicy();
// 4. DiscardOldestPolicy:丢弃队列最老的任务
new ThreadPoolExecutor.DiscardOldestPolicy();
// 5. 自定义拒绝策略
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 记录日志、持久化到数据库等
log.error("Task rejected: {}", r);
}
};
线程池最佳实践:
// CPU 密集型:核心线程数 = CPU 核心数 + 1
int cpuCores = Runtime.getRuntime().availableProcessors();
ExecutorService cpuPool = new ThreadPoolExecutor(
cpuCores + 1, cpuCores + 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(100)
);
// IO 密集型:核心线程数 = CPU 核心数 * 2
ExecutorService ioPool = new ThreadPoolExecutor(
cpuCores * 2, cpuCores * 4,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000)
);
四、JVM 篇
4.1 JVM 内存模型
问题: 讲讲 JVM 内存结构和垃圾回收机制?
答案:
JVM 内存结构:
┌─────────────────────────────────────┐
│ 堆(Heap) │
│ ├─ 年轻代(Young Generation) │
│ │ ├─ Eden 区(8/10) │
│ │ ├─ Survivor0(1/10) │
│ │ └─ Survivor1(1/10) │
│ └─ 老年代(Old Generation) │
├─────────────────────────────────────┤
│ 元空间(Metaspace)JDK 8+ │
│ (之前是永久代 PermGen) │
├─────────────────────────────────────┤
│ 虚拟机栈(VM Stack) │
│ (每个线程私有,栈帧存储局部变量) │
├─────────────────────────────────────┤
│ 本地方法栈(Native Method Stack) │
├─────────────────────────────────────┤
│ 程序计数器(PC Register) │
├─────────────────────────────────────┤
│ 直接内存(Direct Memory) │
│ (NIO 使用,不受 JVM 堆大小限制) │
└─────────────────────────────────────┘
垃圾回收算法:
// 1. 标记-清除(Mark-Sweep)
// 优点:简单
// 缺点:产生内存碎片
// 2. 复制算法(Copying)
// 年轻代使用,Eden → Survivor 的复制
// 优点:无碎片
// 缺点:内存利用率 50%
// 3. 标记-整理(Mark-Compact)
// 老年代使用
// 优点:无碎片
// 缺点:需要移动对象,STW 时间较长
G1 垃圾收集器:
# G1 将堆划分为多个 Region(1MB~32MB)
# 优先回收垃圾最多的 Region
# 启用 G1
-XX:+UseG1GC
# 设置目标最大停顿时间(默认 200ms)
-XX:MaxGCPauseMillis=200
# 设置 Region 大小
-XX:G1HeapRegionSize=16m
4.2 类加载机制
问题: 类的加载过程是怎样的?双亲委派模型是什么?
答案:
类加载过程:
加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载
// 加载:读取 .class 文件,生成 Class 对象
Class<?> clazz = Class.forName("com.example.User");
// 验证:文件格式、元数据、字节码、符号引用验证
// 准备:为类变量分配内存并设置默认值
// static int a = 123; // 准备阶段 a = 0,初始化阶段 a = 123
// 解析:符号引用转直接引用
// 初始化:执行 <clinit>() 方法(类构造器)
双亲委派模型:
┌─────────────────┐
│ Bootstrap │ ← 加载 %JAVA_HOME%/lib 下的类
│ ClassLoader │ (rt.jar 等核心类库)
└────────┬────────┘
│ 委派
┌────────▼────────┐
│ Extension │ ← 加载 %JAVA_HOME%/lib/ext 下的类
│ ClassLoader │
└────────┬────────┘
│ 委派
┌────────▼────────┐
│ Application │ ← 加载 classpath 下的类
│ ClassLoader │ (用户自定义类)
└────────┬────────┘
│ 委派
┌────────▼────────┐
│ Custom │ ← 用户自定义 ClassLoader
│ ClassLoader │
└─────────────────┘
打破双亲委派:
// Tomcat 为每个 Web 应用创建独立的 ClassLoader
// 实现应用间类隔离
public class WebAppClassLoader extends URLClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 1. 先在本地缓存查找
// 2. 本地未找到,自己加载(不先委派给父类)
// 3. 自己加载不到,再委派给父类
}
}
五、Spring 篇
5.1 Spring IoC 容器启动流程
问题: Spring IoC 容器的启动流程是怎样的?
答案:
// 1. 创建 ApplicationContext
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
// 核心启动流程(refresh() 方法)
public void refresh() {
// 1. 准备上下文环境
prepareRefresh();
// 2. 获取 BeanFactory(DefaultListableBeanFactory)
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3. 准备 BeanFactory
prepareBeanFactory(beanFactory);
// 4. 子类扩展(如注册 ServletContextAwareProcessor)
postProcessBeanFactory(beanFactory);
// 5. 执行 BeanFactoryPostProcessor
invokeBeanFactoryPostProcessors(beanFactory);
// 6. 注册 BeanPostProcessor
registerBeanPostProcessors(beanFactory);
// 7. 初始化 MessageSource
initMessageSource();
// 8. 初始化事件广播器
initApplicationEventMulticaster();
// 9. 子类扩展(如创建 Tomcat 服务器)
onRefresh();
// 10. 注册监听器
registerListeners();
// 11. 实例化所有非懒加载的单例 Bean
finishBeanFactoryInitialization(beanFactory);
// 12. 完成刷新
finishRefresh();
}
5.2 Spring Bean 生命周期
问题: Spring Bean 的生命周期是怎样的?
答案:
@Component
public class UserService implements
BeanNameAware,
ApplicationContextAware,
InitializingBean,
DisposableBean {
// 1. 实例化(构造函数)
public UserService() {
System.out.println("1. 构造方法");
}
// 2. 属性赋值(依赖注入)
@Autowired
private UserDao userDao;
// 3. 设置 BeanName
@Override
public void setBeanName(String name) {
System.out.println("2. BeanNameAware.setBeanName: " + name);
}
// 4. 设置 ApplicationContext
@Override
public void setApplicationContext(ApplicationContext ctx) {
System.out.println("3. ApplicationContextAware.setApplicationContext");
}
// 5. @PostConstruct 方法
@PostConstruct
public void postConstruct() {
System.out.println("4. @PostConstruct");
}
// 6. InitializingBean.afterPropertiesSet
@Override
public void afterPropertiesSet() {
System.out.println("5. InitializingBean.afterPropertiesSet");
}
// 7. 自定义 init-method
public void customInit() {
System.out.println("6. custom init-method");
}
// 销毁阶段
@PreDestroy
public void preDestroy() {
System.out.println("7. @PreDestroy");
}
@Override
public void destroy() {
System.out.println("8. DisposableBean.destroy");
}
}
BeanPostProcessor 扩展点:
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
// 初始化之前处理(如 @PostConstruct 之前)
System.out.println("BeforeInitialization: " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// 初始化之后处理(如 AOP 代理在此创建)
System.out.println("AfterInitialization: " + beanName);
return bean;
}
}
5.3 Spring 事务传播行为
问题: Spring 事务的传播行为有哪些?
答案:
@Service
public class OrderService {
@Autowired
private PaymentService paymentService;
// REQUIRED(默认):当前有事务就加入,没有就新建
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder() {
// 业务逻辑
paymentService.pay(); // 加入当前事务
}
// REQUIRES_NEW:挂起当前事务,新建独立事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOperation() {
// 独立事务,不受外部事务回滚影响
}
// NESTED:在当前事务中创建保存点,可独立回滚
@Transactional(propagation = Propagation.NESTED)
public void updateInventory() {
// 失败只回滚到保存点,外部事务继续
}
// SUPPORTS:有事务就加入,没有就以非事务执行
@Transactional(propagation = Propagation.SUPPORTS)
public void queryData() {
// 查询操作不需要事务
}
// MANDATORY:必须在事务中执行,否则抛异常
@Transactional(propagation = Propagation.MANDATORY)
public void mandatoryOperation() {
// 强制要求有事务
}
// NOT_SUPPORTED:挂起当前事务,以非事务执行
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void sendNotification() {
// 发送通知不需要事务
}
// NEVER:必须在非事务中执行,否则抛异常
@Transactional(propagation = Propagation.NEVER)
public void neverInTransaction() {
// 禁止在事务中执行
}
}
事务失效场景:
@Service
public class UserService {
// ❌ 失效:同类方法调用,@Transactional 不生效
public void outer() {
inner(); // 实际是 this.inner(),不经过代理
}
@Transactional
public void inner() {
// 事务不生效
}
// ✅ 解决:注入自身代理对象
@Autowired
private UserService self;
public void outer() {
self.inner(); // 通过代理调用
}
}
六、Spring Boot 篇
6.1 自动装配原理
问题: Spring Boot 自动装配的原理是什么?
答案:
// @SpringBootApplication =
// @Configuration + @EnableAutoConfiguration + @ComponentScan
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// @EnableAutoConfiguration 导入 AutoConfigurationImportSelector
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
自动装配流程:
// 1. 读取 META-INF/spring.factories
// key: org.springframework.boot.autoconfigure.EnableAutoConfiguration
// 2. 过滤条件(@Conditional)
@ConditionalOnClass(DataSource.class) // 类路径有 DataSource
@ConditionalOnMissingBean(DataSource.class) // 容器中没有 DataSource Bean
@ConditionalOnProperty(prefix = "spring.datasource", name = "url") // 配置存在
public class DataSourceAutoConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}
自定义 Starter:
// 1. 创建自动配置类
@Configuration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(MyProperties properties) {
return new MyService(properties);
}
}
// 2. 创建 META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyAutoConfiguration
// 3. 使用方引入依赖即可自动装配
七、总结
本文涵盖了 Java 面试的核心知识点:
| 模块 | 重点内容 |
|---|---|
| Java 基础 | HashMap、ConcurrentHashMap、volatile |
| 并发编程 | synchronized 锁升级、AQS、线程池 |
| JVM | 内存模型、GC 算法、类加载机制 |
| Spring | IoC 启动流程、Bean 生命周期、事务传播 |
| Spring Boot | 自动装配原理 |
面试建议:
- 理解原理比背答案更重要
- 结合实际项目经验回答问题
- 多画图帮助表达(类加载、锁升级、Bean 生命周期等)
💬 觉得有用的话,点个赞+收藏,关注我,每周持续更新实战教程!
标签:java | 面试 | spring | jvm | 并发编程 | 后端
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)