Java后端面试题(2026春季版)
2026/6/26 5:00:14 网站建设 项目流程

一、 Java 基础与集合框架深度

String、StringBuilder 与 StringBuffer 的区别及底层原理

  • 问题: 创建了几个对象?String 为什么是不可变的?

  • 参考答案:

  • 对象创建:创建了 2 个对象。一个是字符串常量池中的 ,另一个是堆内存中通过 创建的 String 对象。

  • 不可变性:String 类被 修饰,且其内部存储字符的数组 也是 修饰的,一旦初始化后引用不可变。这保证了多线程安全和字符串常量池的可靠性。

  • 线程安全:StringBuffer 的核心方法加了 ,是线程安全的但性能较低;StringBuilder 没有同步锁,性能最高,但线程不安全。

HashMap 底层原理、扩容机制与 Key 的要求

  • 问题:JDK 1.8 中 HashMap 的底层数据结构是什么?为什么链表长度大于 8 时转为红黑树?

  • 参考答案:

  • 底层结构:数组 + 链表 + 红黑树。

  • 树化原因:基于泊松分布,在理想哈希下链表长度达到 8 的概率极低。当发生严重哈希碰撞时,链表查询时间复杂度会退化为 O(n),转为红黑树后可优化为 O(log n)。

  • 阈值设定:转为红黑树的阈值是 8,退化回链表的阈值是 6。这是因为红黑树节点占用空间是链表节点的 2 倍,保留 6 是为了避免频繁的树化与退化转换。

  • Key 的要求:作为 Key 的对象必须正确重写 和 方法。如果只重写 而 使用 Object 默认的内存地址计算,会导致存入和取出时哈希桶位置不一致,从而无法获取数据。

HashMap 的扩容机制与容量设计

  • 问题:HashMap 什么时候会进行扩容?为什么容量必须是 2 的幂次方?

  • 参考答案:

  • 扩容触发:当 (阈值 = 容量 × 负载因子,默认 0.75)时触发扩容。扩容会将容量翻倍,并将旧数组元素重新 Hash 到新数组(Rehash)。

  • 2的幂次方原因:当容量为 2 的幂次方时, 等价于 。位运算效率远高于取模运算,且能保证哈希分布均匀。

并发集合 ConcurrentHashMap

  • 问题:ConcurrentHashMap 在 JDK 1.7 和 1.8 中的实现有何不同?

  • 参考答案:

  • JDK 1.7:采用分段锁(Segment + ReentrantLock),每个 Segment 管理一段哈希桶,并发度取决于 Segment 的数量。

  • JDK 1.8:放弃了 Segment,采用 数组。锁粒度细化到了单个哈希桶的头节点。并发插入时,若桶为空使用 CAS,若桶非空则使用 锁住头节点,大幅提升了并发性能。

点击下方小卡片免费获取Java资料Java面试资料https://mp.weixin.qq.com/s/iBmSycbmeTCSRNKJQ10EuA

二、 并发编程与锁机制

线程安全的实现方式

  • 问题:Java 里面如何实现线程安全?

  • 参考答案:

  • 互斥同步:使用 、 等加锁机制,保证同一时刻只有一个线程访问共享资源。

  • 非阻塞同步:基于冲突检测的乐观并发策略,如 CAS(Compare And Swap)操作和 原子类。

  • 无同步方案:使用不可变对象(如 String)、线程本地存储(ThreadLocal)或栈封闭(方法内的局部变量)。

synchronized 底层原理与锁升级

  • 问题: 底层是怎么实现的?偏向锁、轻量级锁了解吗?

  • 参考答案:

  • 底层实现:JVM 通过对象头中的 Mark Word 记录锁状态。同步代码块通过 和 指令实现;同步方法通过 ACC_SYNCHRONIZED 标志位实现。

  • 锁升级:

  • 偏向锁:无竞争时,Mark Word 记录线程 ID,后续该线程进入无需 CAS。

  • 轻量级锁:出现竞争时,线程在栈帧创建 Lock Record,通过 CAS 尝试将 Mark Word 指向该记录。失败则自旋等待。

  • 重量级锁:自旋超过阈值,膨胀为重量级锁,未获取锁的线程被操作系统挂起(阻塞),开销最大。

AQS 与 ReentrantLock

  • 问题: 和 有什么区别?什么是 AQS?

  • 参考答案:

  • 区别: 是 JVM 关键字,自动释放锁,非公平; 是 API 层面的锁,需在 finally 手动释放,支持公平锁、可中断、多 Condition 条件变量。

  • AQS (AbstractQueuedSynchronizer):JUC 并发包的灵魂。核心是一个 变量和一个 CLH 双向阻塞队列。通过模板方法模式,子类只需实现 即可实现各种同步器(如 ReentrantLock、CountDownLatch)。

线程池核心原理与线上踩坑

  • 问题:线程池的核心参数有哪些?任务提交后的执行流程是怎样的?

  • 参考答案:

  • 七大参数:核心线程数、最大线程数、空闲存活时间、时间单位、工作队列、线程工厂、拒绝策略。

  • 执行流程:新任务提交 -> 若线程数 < 核心线程数,创建核心线程 -> 若核心线程满,放入工作队列 -> 若队列满,且线程数 < 最大线程数,创建非核心线程 -> 若达到最大线程数且队列满,执行拒绝策略。

  • 拒绝策略:AbortPolicy(抛异常,默认)、CallerRunsPolicy(调用者线程执行)、DiscardPolicy(丢弃)、DiscardOldestPolicy(丢弃最老任务)。

ThreadLocal 原理与内存泄漏

  • 问题:ThreadLocal 的实现原理是什么?为什么会内存泄漏?

  • 参考答案:

  • 原理:每个 Thread 内部维护一个 ,Key 是 ThreadLocal 对象,Value 是存储的值。

  • 内存泄漏:Map 的 Key(ThreadLocal)是弱引用,Value 是强引用。当 ThreadLocal 外部没有强引用时,Key 会被 GC 回收,但 Value 永远不会被回收。如果线程长期存活(如线程池),Value 就会一直存在导致内存泄漏。解决方式:使用完毕后务必调用 方法。

线上故障排查实战

  • 问题:线上服务 CPU 飙高或发生死锁,如何排查?

  • 参考答案:

  • CPU 飙高: 找最高 CPU 线程 -> 转 16 进制 -> 查看堆栈定位代码。也可使用 Arthas 的 直接查看最忙线程。

  • 死锁排查:使用 查看输出,JVM 会自动检测并打印 "Found one Java-level deadlock" 及互相等待的锁信息。

三、 JVM 原理与性能调优

JVM 内存模型与垃圾回收

  • 问题:JVM 内存分哪几块?G1 垃圾收集器与 CMS 有什么区别?

  • 参考答案:

  • 内存划分:堆(新生代 Eden/Survivor、老年代)、方法区(元空间)、虚拟机栈、本地方法栈、程序计数器。

  • G1 vs CMS:CMS 基于标记-清除,会产生内存碎片;G1 将堆划分为多个大小相等的 Region,逻辑分代但物理不连续,采用标记-整理算法避免碎片。G1 通过 RSet 解决跨 Region 引用,支持可预测的停顿时间模型(MaxGCPauseMillis)。

类加载机制与双亲委派

  • 问题:了解 Java 的双亲委派机制吗?类加载过程是怎样的?

  • 参考答案:

  • 加载过程:加载 -> 验证 -> 准备(分配内存并赋零值) -> 解析(符号引用转直接引用) -> 初始化(执行 )。

  • 双亲委派:类加载请求先委托父加载器,父加载器无法完成时才自己加载。优点:保证 Java 核心类库的安全性和唯一性。

  • 打破场景:JDBC SPI 机制(使用线程上下文类加载器)、Tomcat 的 Web 容器隔离、OSGi 模块化框架。

四、 数据库、事务与 SQL 优化

MySQL 索引原理与数据结构

  • 问题:为什么 MySQL 索引使用 B+ 树而不是 B 树或 Hash?

  • 参考答案:

  • 对比 B 树:B+ 树非叶子节点只存键值和指针,不存数据,因此单页能容纳更多索引,树更矮胖,大幅减少磁盘 IO 次数。

  • 对比 Hash:Hash 索引仅支持等值查询,不支持范围查询和排序。B+ 树叶子节点通过双向链表连接,完美支持范围查询(Range Query)。

事务隔离级别与并发问题

  • 问题:MySQL 事务的隔离级别有哪些?分别解决什么问题?

  • 参考答案:

  • 读未提交:存在脏读、不可重复读、幻读。

  • 读已提交 (RC):解决脏读,存在不可重复读、幻读。

  • 可重复读 (RR):InnoDB 默认级别。解决脏读、不可重复读。通过 MVCC + 间隙锁(Gap Lock)解决大部分幻读问题。

  • 串行化:全部解决,但并发性能极差。

MVCC 与锁机制协同

  • 问题:MVCC 的实现原理是什么?UndoLog 和 RedoLog 的作用和区别?

  • 参考答案:

  • MVCC:通过隐藏字段(事务 ID、回滚指针)+ Undo Log 版本链 + ReadView(读视图)实现。快照读(普通 Select)使用 MVCC 实现非阻塞;当前读(Update/For Update)使用 Next-Key Lock(临键锁)保证互斥。

  • Undo Log:逻辑日志,记录数据的反向操作。用于事务回滚和 MVCC 的历史版本链构建。

  • Redo Log:物理日志,记录“在某个数据页做了什么修改”。用于崩溃恢复(Crash-safe),采用 WAL(Write-Ahead Logging)机制,将随机 IO 转为顺序 IO。

SQL 优化与慢查询排查

  • 问题:如果出现慢查询,如何进行优化?索引失效的场景有哪些?

  • 参考答案:

  • 排查步骤:开启慢查询日志 -> 使用 分析执行计划(重点看 type 是否为 ALL,key 是否为 NULL,Extra 是否有 Using filesort/Using temporary)。

  • 索引失效场景:对索引列进行函数运算或类型隐式转换、违反最左前缀原则、使用 或 、 以 开头、 前后有非索引列。

点击下方小卡片免费获取Java面试资料Java面试资料https://mp.weixin.qq.com/s/iBmSycbmeTCSRNKJQ10EuA

五、 核心框架 (Spring / Spring Boot / MyBatis)

Spring Bean 生命周期与循环依赖

  • 问题:Spring Bean 的生命周期是怎样的?循环依赖怎么解决的?

  • 参考答案:

  • 生命周期:实例化 -> 属性填充 -> Aware 接口回调 -> BeanPostProcessor 前置处理 -> 初始化 -> BeanPostProcessor 后置处理 -> 销毁。

  • 循环依赖:通过三级缓存解决。一级缓存存成品 Bean,二级缓存存半成品 Bean,三级缓存存 ObjectFactory。当 A 依赖 B,B 又依赖 A 时,B 可以从三级缓存中获取 A 的早期引用(若需要 AOP,此时会提前触发 AOP 代理),从而打破循环。

Spring 事务失效场景

  • 问题:加了 为什么有时候不生效?

  • 参考答案:

  • 同类方法调用: 绕过了代理对象,事务失效。

  • 方法非 public:Spring AOP 默认只拦截 public 方法。

  • 异常被吞:内部 捕获了异常未抛出,Spring 无法感知。

  • 异常类型错误:默认只对 回滚,抛出受检异常需配置 。

  • 多线程:事务上下文绑定在当前线程,新线程无法继承事务。

Spring Boot 自动装配与 AOP 原理

  • 问题:Spring Boot 自动配置原理是什么?AOP 是如何实现的?

  • 参考答案:

  • 自动装配: 导入 ,读取 文件,结合 系列注解按需加载配置类。

  • AOP 实现:基于动态代理。如果目标对象实现了接口,默认使用 JDK 动态代理;否则使用 CGLIB 通过生成子类字节码实现代理。

MyBatis 防 SQL 注入

  • 问题:MyBatis 中如何防止 SQL 注入?

  • 参考答案:使用 占位符,底层使用 预编译,参数会被当作字符串处理,有效防止注入。严禁使用 ,它只是简单的字符串拼接。

六、 中间件 (Redis / MQ)

Redis 数据类型与 Zset 底层

  • 问题:Redis 常见的数据结构有哪些?Zset 底层怎么实现的?

  • 参考答案:

  • 基础类型:String、List、Hash、Set、ZSet。

  • Zset 底层:当元素较少且长度较短时使用 ZipList(压缩列表);否则使用 SkipList(跳表)+ Dict(字典)。字典用于 O(1) 查找分数,跳表用于 O(log N) 的范围排序查询。

缓存穿透、击穿、雪崩

  • 问题:这三个问题分别是什么?如何解决?

  • 参考答案:

  • 穿透(查不存在的数据):缓存空值(短过期)、布隆过滤器。

  • 击穿(热点 Key 过期):互斥锁()重建缓存、逻辑过期(不设置 TTL,后台异步更新)。

  • 雪崩(大量 Key 同时过期):过期时间加随机值、多级缓存、Redis 高可用集群。

Redis 持久化与数据一致性

  • 问题:Redis 持久化方案有哪些?如何保证缓存与数据库双写一致性?

  • 参考答案:

  • 持久化:RDB(快照,恢复快但易丢数据)、AOF(追加日志,安全但文件大)。生产推荐混合持久化(RDB 全量 + AOF 增量)。

  • 双写一致性:业界铁律是“先更新数据库,再删除缓存”。为防止并发脏数据,可采用“延时双删”或基于 Binlog(Canal + MQ)异步删除缓存的最终一致性方案。

Kafka 高可用与消息积压

  • 问题:Kafka 如何保持高可用?遇到消息积压怎么处理?

  • 参考答案:

  • 高可用:依赖 Partition 多副本机制和 ISR(In-Sync Replicas)同步列表。Leader 挂掉时,从 ISR 中选举新 Leader。

  • 消息积压:紧急扩容消费者;若消费者已达上限,新建一个 Topic(Partition 数 = 原数 × 10),将积压消息转发过去,再启动 10 倍数量的消费者快速消费。

七、 实际场景与系统设计问题

秒杀系统设计

  • 问题:设计一个秒杀系统,需要考虑哪些问题?

  • 参考答案:

  • 核心原则:动静分离、请求拦截、异步削峰。

  • 技术栈:CDN 加速静态资源 -> Nginx 限流 -> Redis 预扣库存(Lua 脚本保证原子性) -> 消息队列(Kafka/RabbitMQ)异步下单 -> MySQL 扣减真实库存。

  • 防超卖:数据库层使用乐观锁()。

分布式事务

  • 问题:微服务下如何解决跨库数据一致性?TCC 的空回滚和悬挂问题怎么解决?

  • 参考答案:

  • 方案选型:强一致用 2PC/Seata AT;高并发最终一致用 MQ 可靠消息或 TCC。

  • TCC 异常处理:

  • 空回滚:Cancel 接口执行时,Try 还没执行。解决:Cancel 中检查 Try 是否执行过,未执行则直接返回成功。

  • 悬挂:Cancel 比 Try 先执行。解决:Try 执行前检查是否已经执行过 Cancel,若执行过则 Try 直接返回,不插入业务数据。

架构分层与流量控制

  • 问题:为什么复杂的架构一定要做分层设计?滑动窗口限流如何实现?

  • 参考答案:

  • 分层设计:解耦(Controller/Service/Dao)、复用、便于独立扩展和测试、隔离故障域。

  • 滑动窗口:将时间轴划分为多个小格子(如 1 秒分 10 个 100ms 的格子)。统计时,根据当前时间动态计算窗口内格子的请求总和。相比固定窗口,解决了边界突刺问题。

分布式锁防过期

  • 问题:分布式锁如何防止业务执行时间过长导致锁提前过期?

  • 参考答案:引入看门狗机制(Watchdog)。在获取锁成功后,启动一个后台定时任务(如 Redisson),每隔锁过期时间的 1/3(如 10 秒)自动续期。只要持有锁的客户端不宕机,锁就不会过期。

  • 由于篇幅有限,完整资料点击下方小卡片免费获取Java面试资料https://mp.weixin.qq.com/s/iBmSycbmeTCSRNKJQ10EuA

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询