Java面试题全网最全整理(附答案),已按模块分类,从基础到实战一篇搞定
最近帮团队面了 20 多个 3-5 年经验的 Java 候选人,发现一个共性问题:简历上列满了 "精通 SpringBoot"" 熟练 Redis""熟悉分布式事务",但一深问就露馅 —— 说懂 HashMap,却讲不清红黑树转链表的阈值为什么是 8;提过项目用了缓存,却答不出缓存和数据库一致性的具体解决方案。
3-5 年这个阶段,面试官早已不满足于 "你用过什么",而是要看 "你吃透了什么",更重要的是 "你怎么用技术解决实际问题"。本文结合近百场面试经验,拆解这个阶段的核心考察点,附上面试官期待的 "深度回答模板" 和 "项目结合案例"。
一、Java 基础与并发:从 "会用" 到 "懂原理"
1. HashMap 源码:不止是 "数组 + 链表 + 红黑树"
初级回答:"HashMap 底层是数组加链表,JDK1.8 后当链表长度超过 8 会转红黑树,默认负载因子 0.75,初始容量 16。"(这是应届生都该知道的)
3 年 + 该有的深度:
- 为什么链表转红黑树的阈值是 8?"因为 HashMap 的作者通过泊松分布计算,链表长度超过 8 的概率低于千万分之一,所以用 8 作为转树阈值,平衡查询效率和内存开销。而转回链表的阈值是 6,避免频繁在树和链表间转换( hysteresis 机制)。"
- 项目中的实际应用:"我们订单系统之前用 HashMap 存用户购物车,发现高并发下偶尔出现死循环。排查后发现是 JDK1.7 的头插法在扩容时导致的循环链表,后来升级 JDK1.8 并用 ConcurrentHashMap 替代,同时初始化时指定容量(如 new HashMap<>(1024))减少扩容次数,性能提升了 30%。"
2. synchronized 锁升级:从偏向锁到重量级锁的触发条件
必问点:JDK1.6 后 synchronized 的优化机制。
深度回答:" 锁升级是 JVM 对 synchronized 的性能优化,过程是无锁→偏向锁→轻量级锁→重量级锁:
- 偏向锁:单线程场景下,通过 CAS 在对象头 Mark Word 记录线程 ID,后续该线程可直接获取锁,避免每次 CAS 操作。
- 轻量级锁:当有第二个线程竞争时,偏向锁撤销,线程通过 CAS 将 Mark Word 替换为锁记录指针,此时线程交替执行(自旋),不阻塞。
- 重量级锁:当竞争加剧(自旋次数超过 10 或线程数超过 CPU 核心数一半),轻量级锁膨胀为重量级锁,依赖操作系统 mutex 锁实现,线程会阻塞挂起。
项目中我们在用户登录接口用了 synchronized,初期以为会性能差,后来发现多数情况是单用户操作,锁停留在偏向锁状态,实际性能损耗很小。但当遇到爬虫高频请求时,锁升级为重量级锁,接口响应变慢,最后用 Redis 分布式锁替代解决了问题。"
3. 线程池:不止是 7 个参数,而是 "动态调整" 的智慧
面试官想听的不是参数背诵,而是 "为什么这么配置"。
结合项目的回答:" 我们支付系统的线程池配置是核心线程数 10,最大线程数 20,队列用 ArrayBlockingQueue (1000),拒绝策略 CallerRunsPolicy。
- 核心线程数 10:根据压测,每秒 100 笔支付请求,每笔请求平均耗时 100ms,10 个线程刚好能扛住(100×0.1=10)。
- 最大线程数 20:应对瞬时峰值(如秒杀时每秒 200 笔),临时线程在峰值过后会在 60 秒后销毁(keepAliveTime=60s)。
- 有界队列:防止订单量暴增时 OOM,队列满时通过 CallerRunsPolicy 让调用线程处理,变相限流。
后来发现凌晨对账时,核心线程长期空闲,通过设置 allowCoreThreadTimeOut (true) 让核心线程超时销毁,减少了 20% 的内存占用。"
二、JVM:从 "理论" 到 "实战调优"
1. G1 收集器:如何把 Full GC 控制在 50ms 内
初级回答:"G1 是区域化分代式收集器,把堆分成多个 Region,兼顾吞吐量和延迟。"
3 年 + 该有的调优经验:" 我们订单系统用 G1 时,曾因 Full GC 频繁导致接口超时。排查步骤:
- 用 jstat -gcutil 发现 Old 区占比达 90%,FGC 间隔不到 1 分钟,每次耗时 150ms。
- 分析 heap dump 发现大量 Order 对象未回收,追溯到订单状态机设计缺陷,导致历史订单对象被静态集合引用。
- 修复后仍有偶发 FGC,通过 - XX:InitiatingHeapOccupancyPercent=40(默认 45)让 Mixed GC 提前触发,同时调大 - XX:G1HeapRegionSize=16m(原 8m),减少大对象跨 Region 分配的碎片。
最终 FGC 消失,Young GC 耗时稳定在 20ms 内。"
2. 内存泄漏:从 "知道" 到 "定位解决"
面试官想听到的排查链路:
" 之前用户中心服务频繁 OOM,排查过程:
- 用 jmap -histo:live <pid>发现 char [] 占比 60%,推测是字符串缓存未清理。
- 生成堆快照(jmap -dump)后用 MAT 分析,发现一个 HashMap 中存储了千万级用户 Token,且没有过期清理机制。
- 追溯代码发现是早期为了减少 Redis 访问,用 HashMap 做了本地缓存,但忘了设置过期时间。
- 解决:改用 Caffeine 缓存(设置 expireAfterWrite=30 分钟),并限制最大容量 10 万,内存占用从 4G 降到 1.5G。"
三、框架:从 "会用注解" 到 "懂源码设计"
1. Spring 事务:为什么加了 @Transactional 却没生效?
初级回答:"可能是方法不是 public,或者没加 @Service 注解。"
3 年 + 该有的踩坑经验:" 项目中遇到过三种事务失效场景:
- 自调用问题:同一类中 A 方法调用 B 方法(B 加了 @Transactional),因未经过 Spring 代理,事务不生效。解决:用 AopContext.currentProxy () 获取代理对象调用。
- 异常被捕获:B 方法中 try-catch 了异常但未抛出,事务无法感知。解决:在 catch 中手动 throw new RuntimeException (),或设置 rollbackFor=Exception.class。
- 多线程调用:A 方法启动新线程调用 B 方法,B 的事务和 A 不在同一线程,无法回滚。解决:用消息队列保证最终一致性,或分布式事务。"
2. SpringBoot 自动装配:为什么引入 starter 就能用?
深度回答:" 自动装配的核心是 @EnableAutoConfiguration,它通过 @Import 导入 AutoConfigurationImportSelector,这个类会加载 META-INF/spring.factories 中的自动配置类(如 RedisAutoConfiguration)。
这些配置类通过 @Conditional 注解判断是否生效,比如 RedisAutoConfiguration 上的 @ConditionalOnClass (RedisOperations.class),只有类路径存在 RedisOperations 时才会生效。
我们项目自定义了支付 starter,核心是在 spring.factories 中配置 PayAutoConfiguration,并用 @ConditionalOnProperty (prefix="pay", name="enabled", havingValue="true") 让用户通过配置开关控制,同时用 @ConditionalOnMissingBean 允许用户自定义支付实现类。"
四、数据库与缓存:从 "会写 SQL" 到 "懂优化本质"
1. MySQL 索引:为什么加了索引还是慢?
结合案例的回答:" 订单表(1000 万行)查用户最近 30 天的订单,加了索引 idx_user_create_time (user_id, create_time) 还是慢,排查发现:
- 索引失效:SQL 用了 create_time > now () - interval 30 day,虽然联合索引前缀 user_id 是等值查询,但函数操作导致索引失效?不,这里是范围查询,联合索引中范围条件后的字段无法使用索引,但前缀 user_id 是有效的。
- 实际原因:用户 ID 是高频用户(如商家账号),该用户的订单占了 300 万行,即使走索引,扫描 300 万行还是慢。
- 优化:按 create_time 分表(每月一张),查询时先定位到最近 30 天所在的表,再用索引,查询时间从 500ms 降到 50ms。"
2. Redis 缓存:如何解决缓存与数据库一致性问题?
不是只说 "先删缓存再更新 DB",而是分场景:
" 我们根据业务一致性要求选择方案:
- 商品详情页(允许短暂不一致):用 ' 更新 DB 后删缓存 ',配合 Redis 过期时间兜底。删缓存失败时用消息队列重试,避免缓存脏数据。
- 库存系统(强一致性):用 ' 分布式锁 + 先更新 DB 再更缓存 ',更新 DB 时加行锁,保证同一商品的库存更新串行执行,缓存更新失败则回滚 DB。
- 历史订单查询(最终一致性):异步更新缓存,DB 更新后发消息到 RocketMQ,消费者更新缓存,失败则死信队列重试,适合非实时场景。"
五、分布式:从 "知道概念" 到 "落地权衡"
1. 分布式事务:不是只有 TCC,而是 "选对方案"
面试官想听的是权衡思维:
" 我们不同场景用了不同方案:
- 转账业务(强一致性):用 Seata 的 AT 模式,基于 undo log 自动回滚,开发成本低,性能满足每秒 500 笔的需求。
- 订单创建(最终一致性):用本地消息表,订单表插入时同时写消息表,本地事务保证两者原子性,然后异步发送消息给库存服务,失败则定时任务重试。
- 积分兑换(高并发):用 TCC 模式,Try 阶段冻结积分和库存,Confirm 实际扣减,Cancel 解冻,适合秒杀场景(每秒 2000 单),但开发成本高,需要手写 3 个接口。"
2. 消息队列:如何保证消息不丢失、不重复?
项目实战经验:" 基于 RocketMQ 的方案:
- 不丢失:
- 生产者:用同步发送 + 事务消息,发送失败重试 3 次,确保消息到 Broker。
- Broker:开启持久化(刷盘策略 ASYNC_FLUSH),主从同步,避免单点故障。
- 消费者:关闭 autoCommit,处理完业务再手动 ack,失败则稍后重试。
- 不重复:消费者端用消息 ID + 业务唯一键(如订单号)做幂等,存 Redis(set nx 5 分钟),重复消息直接返回成功。
我们在秒杀场景中,即使消息重复,也能保证库存不超卖,因为扣减库存时会检查 ' 库存 >= 购买量 ',且用了 Redis 分布式锁。"
六、3-5 年面试的 3 个突围技巧
- 用 "问题 - 方案 - 数据" 结构讲项目:别说 "我负责订单系统",而要说 "订单系统在秒杀时 QPS 从 500 涨到 5000,出现库存超卖和接口超时(问题),我引入 Redis 预扣库存 + 消息队列异步下单 + 本地缓存热点商品(方案),最终支撑了 10 万单 / 秒,接口响应时间从 800ms 降到 50ms(数据)"。
- 主动暴露 "踩坑经历":面试官喜欢听你解决过的问题,比如 "之前用 HashMap 存缓存导致内存泄漏,后来换成 Caffeine 并设置过期时间",这比说 "我精通各种缓存" 更可信。
- 讲清 "技术选型的权衡":比如 "为什么不用 TCC 而用本地消息表?因为团队当时对 TCC 理解不深,本地消息表更易实现,虽然一致性弱一点,但满足业务需求,后期再迭代优化"—— 体现务实的工程思维。
结语:3-5 年,拼的是 "技术深度 × 项目落地能力"
这个阶段的面试,早已不是 "背源码"" 列技术栈 " 能应付的。面试官更看重你对技术的理解深度(如为什么这么设计)、解决问题的思路(如排查 OOM 的步骤),以及在项目中如何权衡技术选型(如一致性与性能的取舍)。
把每个技术点和实际项目结合,讲清 "你遇到了什么问题,用什么技术解决,为什么这么选,带来了什么效果",这才是 3-5 年 Java 后端突围的关键。祝你面试顺利,拿到心仪的薪资!
面试题笔记分享
为了助力朋友们跳槽面试、升职加薪、职业困境,提高自己的技术,本文给大家整了一套涵盖Java后端面试所有技术栈的快速学习方法和笔记。目前已经收到了七八个网友的反馈,说是面试问到了很多这里面的知识点。
通过大数据总结发现,其实Java后端面试都是差不多的。常问的有下面这几块知识点:
【文末可以无偿领取Java后端面试全套资料】
基础篇
- Java语言有哪些特点?
- 面向对象和面向过程的区别?
- 八种基本数据类型的大小,以及他们的封装类?
- 标识符的命名规则?
- instanceof关键字的作用重载和重写的区别?
- equals与==的区别?
JVM篇
- 类加载与卸载?
- 简述一下JVM的内存模型?
- 堆和栈的区别?
- 什么时候会触发FullGC?
- 什么是Java虚拟机?为什么Java被称作是"平台无关的编程语言"?
- Java内存结构?
多线程&并发篇
- Java中实现多线程有几种方法?
- 如何停止一个正在运行的线程?
- notify()和notifyAll()有什么区别?
- sleep()和wait()有什么区别?
- volatile 是什么?可以保证有序性吗?
- Thread 类中的start()和run()方法有什么区别?
Spring篇
- Spring的IOC和AOP机制?
- Spring中Autowired和Resource关键字的区别?
- 依赖注入的方式有几种,各是什么?
- 讲一下什么是Spring?
- Spring MVC流程?
- SpringMVC怎么样设定重定向和转发的?
MyBatis篇
- 什么是MyBatis?
- MyBatis的优点和缺点?
- #和$的区别是什么?
- 当实体类中的属性名和表中的字段名不一样,怎么办?
- Mybatis是如何进行分页的?分页插件的原理是什么?
SpringBoot篇
- 什么是SpringBoot?为什么要用SpringBoot
- Spring Boot的核心注解是哪个?它主要由哪几个注解组成的?
- 运行Spring Boot有哪几种方式?
- 如何理解Spring Boot 中的Starters?
MySQL篇
- 数据库的三范式是什么?
- 数据库引擎有哪些?
- InnoDB与MyISAM的区别?
- 数据库的事务?
- 索引问题?
- SQL优化?
Redis篇
- Redis持久化机制?
- 缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题?
- 热点数据和冷数据是什么?
- Memcache与Redis的区别都有哪些?
SpringCloud篇
- 什么是SpringCloud?
- 什么是微服务?
- SpringCloud有什么优势?
- 什么是服务熔断?什么是服务降级?
Nginx篇
- 简述—下什么是Nginx,它有什么优势和功能?
- Nginx是如何处理一个HTTP请求的呢?
- 列举—些Nginx的特性?
- 请列举Nginx和Apache之间的不同点?
zookeeper篇
- ZooKeeper 是什么?
- ZooKeeper提供了什么?
- Zookeeper 文件系统?
- ZAB 协议?
- 四种类型的数据节点Znode?
- ZookeeperWatcher机制-数据变更通知?
kafka篇
- 如何获取topic主题的列表?
- 生产者和消费者的命令行是什么?
- consumer是推还是拉?
- 讲讲kafka维护消费状态跟踪的方法
- 讲一下主从同步?
MQ篇
- 为什么使用MQ
- MQ优缺点?
- 如何保证高可用的?
- 如何保证消息的顺序?
Elasticsearch篇
- elasticsearch了解多少,说说你们公司es的集群架构,索引数据大小,分片有多少,以及一些调优手段。
- elasticsearch 的倒排索引是什么
- elasticsearch索引数据多了怎么办,如何调优,部署
- elasticsearch是如何实现 master 选举的
Linux篇
- 绝对路径用什么符号表示?当前目录、上层目录用什么表示?主目录用什么表示?切换目录用什么命令?
- 怎么查看当前进程?怎么执行退出?怎么查看当前路径?
- 怎么清屏?怎么退出当前命令?怎么执行睡眠?怎么查看当前用户id?查看指定帮肋用什么
- Ls命令执行什么功能?可以带哪些参数,有什么区别?
- 建立软链接(快捷方式),以及硬链接的命令。
最后作为一位过来人也是希望大家少走一些弯路,在这里我给大家分享一些Java后端面试的学习资料,这些资料希望能给你前进的路上带来帮助。【点击下方名片无偿领取Java后端面试全套资料】
