本文是 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 优化:

  1. 链表转红黑树:当链表长度 ≥ 8 且数组长度 ≥ 64 时,链表转为红黑树,查询复杂度从 O(n) 降到 O(log n)
  2. 尾插法:插入时使用尾插法替代头插法,避免并发扩容时的死循环
  3. 扩容优化:扩容时不需要重新计算 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 的变化");
    }
}

实现原理:

  1. 可见性:写 volatile 变量会立即刷新到主内存,读 volatile 变量会从主内存刷新
  2. 有序性:插入内存屏障(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   // 拒绝策略
)

任务执行流程:

  1. 当前运行线程 < corePoolSize:创建新线程执行任务
  2. 当前运行线程 ≥ corePoolSize:任务进入队列
  3. 队列已满且线程 < maximumPoolSize:创建非核心线程
  4. 队列已满且线程 ≥ 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 自动装配原理

面试建议:

  1. 理解原理比背答案更重要
  2. 结合实际项目经验回答问题
  3. 多画图帮助表达(类加载、锁升级、Bean 生命周期等)

💬 觉得有用的话,点个赞+收藏,关注我,每周持续更新实战教程!

标签:java | 面试 | spring | jvm | 并发编程 | 后端

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐