1. 面试题准备的必要性
作为有2-5年经验的Java后端开发者,面试准备是职业发展的重要环节。这个阶段的开发者已经掌握了基础语法和框架使用,但往往缺乏系统性的知识梳理。面试题整理不仅能帮助应对技术考察,更能查漏补缺,建立完整的知识体系。
我在多次面试候选人时发现,中级开发者最容易在底层原理和系统设计环节失分。比如能说出HashMap的用法,但讲不清楚扩容机制;能使用Spring注解,但解释不清AOP的实现原理。这些问题恰恰反映了日常开发中"知其然不知其所以然"的普遍现象。
2. Java基础核心考点解析
2.1 集合框架深度剖析
ArrayList和LinkedList的区别是高频考点,但仅回答"一个数组实现一个链表实现"远远不够。面试官期待的是:
- 扩容成本:ArrayList默认扩容50%,涉及数组拷贝
- 内存占用:LinkedList每个元素多消耗24字节(前后指针)
- 缓存友好性:ArrayList更好的空间局部性
- 实际场景选择:读多写少用ArrayList,频繁插入删除考虑LinkedList
经验:解释差异时要结合JVM内存模型,比如提到ArrayList对CPU缓存更友好会加分
2.2 HashMap底层原理
这个问题要分层次回答:
- 基础结构:数组+链表/红黑树(JDK8+)
- 哈希计算:h = key.hashCode()) ^ (h >>> 16)
- 解决冲突:拉链法→树化(链表长度>=8且数组长度>=64)
- 扩容机制:2倍扩容,rehash时高位判断(e.hash & oldCap) == 0
常见陷阱问题:"为什么重写equals必须重写hashCode?" 答案在于HashMap的get操作依赖hashCode定位桶,再用equals比较key。
3. 并发编程实战要点
3.1 synchronized实现原理
从三个层面解释:
- 字节码层面:monitorenter/monitorexit指令
- JVM层面:对象头Mark Word中的锁标志位
- 系统调用层面:依赖操作系统的mutex lock
升级过程要讲清楚:无锁→偏向锁→轻量级锁→重量级锁。可以画图说明Mark Word的变化。
3.2 volatile关键字
必须包含以下要点:
- 可见性:通过内存屏障禁止指令重排序
- 不保证原子性:i++操作仍需synchronized
- 底层实现:Lock前缀指令+缓存一致性协议(MESI)
- 典型场景:状态标志位、双重检查锁
踩坑记录:曾遇到volatile修饰数组的情况,实际上只能保证数组引用可见,元素可见性仍需AtomicReferenceArray
4. JVM调优实战
4.1 内存区域划分
重点区分:
- 线程私有:程序计数器、虚拟机栈、本地方法栈
- 线程共享:堆、方法区(元空间)
- 直接内存:NIO使用的Native内存
要能画出内存结构图,并标注各区域可能出现的OOM情况。
4.2 GC算法与回收器
对比表格更直观:
| 回收器 | 算法组合 | 适用场景 | 参数调整 |
|---|---|---|---|
| Serial | 复制+标记整理 | 客户端模式 | -XX:+UseSerialGC |
| Parallel Scavenge | 复制+标记整理 | 吞吐量优先 | -XX:MaxGCPauseMillis |
| CMS | 标记清除 | 低延迟 | -XX:CMSInitiatingOccupancyFraction |
| G1 | 分区算法 | 大堆内存 | -XX:MaxGCPauseMillis |
5. Spring框架深度问题
5.1 Bean生命周期
完整的回答应该包含:
- 实例化:调用构造函数
- 属性赋值:populateBean()
- 初始化:
- Aware接口回调
- BeanPostProcessor前置处理
- InitializingBean.afterPropertiesSet()
- init-method
- BeanPostProcessor后置处理
- 销毁:DisposableBean.destroy()
建议用流程图辅助说明,特别要强调BeanPostProcessor的扩展点。
5.2 事务传播机制
七种传播行为要分类记忆:
- 支持当前事务:REQUIRED(默认)、SUPPORTS、MANDATORY
- 非事务执行:REQUIRES_NEW、NOT_SUPPORTED、NEVER
- 嵌套事务:NESTED(Savepoint机制)
实际案例:在批量处理时,REQUIRES_NEW适合每个子任务独立提交;NESTED适合部分失败时回滚到保存点。
6. 数据库与缓存
6.1 MySQL索引优化
B+树索引要讲清楚:
- 三层B+树可支撑2000万数据(假设页大小16K,主键8B)
- 最左前缀原则:index(a,b,c)能优化a=?、a=? and b=?、a=? and b=? and c=?
- 索引失效场景:函数操作、隐式转换、!=判断
给出实际案例:某用户中心查询缓慢,通过EXPLAIN发现未走联合索引,调整查询顺序后性能提升10倍。
6.2 Redis持久化策略
对比RDB和AOF:
| 维度 | RDB | AOF |
|---|---|---|
| 数据安全 | 可能丢失最后一次快照后的数据 | 最多丢失1秒数据(appendfsync everysec) |
| 恢复速度 | 快 | 慢 |
| 文件体积 | 小(二进制压缩) | 大(文本命令) |
| 性能影响 | 子进程生成快照时CPU/内存开销 | 持续写入的IO压力 |
生产环境建议:主节点关闭持久化,从节点开启RDB+AOF混合模式。
7. 分布式系统核心
7.1 CAP理论应用
分区容错性(P)必须保证,实际是在CP和AP间权衡:
- CP系统:ZooKeeper(选举期间不可用)
- AP系统:Eureka(允许短暂数据不一致)
常见误区:认为可以同时满足CAP三要素,实际上网络分区时只能选择CP或AP。
7.2 分布式锁实现
三种实现方式对比:
- 数据库乐观锁:version字段,适合冲突少的场景
- Redis SETNX:需解决锁续期问题(Redisson看门狗机制)
- Zookeeper:临时顺序节点,实现公平锁
避坑指南:Redis锁要设置随机value,防止其他线程误删;解锁需保证原子性(Lua脚本)
8. 系统设计方法论
8.1 秒杀系统设计
分层优化策略:
- 前端:静态化+按钮置灰+验证码
- 网关:限流(令牌桶算法)
- 服务:缓存预热+库存扣减(Redis原子操作)
- 数据:异步扣库存(消息队列)
要强调库存扣减的原子性方案:Redis DECR+Lua脚本,或者CAS乐观锁。
8.2 服务熔断设计
三个状态转换:
- 关闭状态:正常请求
- 打开状态:直接拒绝请求
- 半开状态:试探性放行部分请求
参数配置建议:
- 滑动窗口大小:10-20个请求
- 错误阈值比例:50%
- 熔断持续时间:5-10秒
9. 编码能力考察
9.1 手写单例模式
给出双重检查锁实现,并解释:
- volatile防止指令重排序
- 第一次判空避免不必要的同步
- 第二次判空防止重复创建
public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }9.2 生产者消费者问题
使用BlockingQueue的实现要点:
- 共享队列设置容量限制
- 生产者put操作自动阻塞
- 消费者take操作自动阻塞
- 优雅停机方案:毒丸对象
class Producer implements Runnable { private final BlockingQueue<Integer> queue; public void run() { try { while (true) { queue.put(produce()); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }10. 项目经验梳理
10.1 技术难点表述
采用STAR法则:
- Situation:千万级订单的库存超卖问题
- Task:保证扣减的准确性和高性能
- Action:引入Redis分布式锁+分段库存方案
- Result:TPS从200提升到5000,零超卖
10.2 架构设计思路
常用表述框架:
- 现状痛点:原有架构的瓶颈
- 设计目标:QPS、RT、可用性指标
- 方案选型:技术对比(如Kafka vs RabbitMQ)
- 落地效果:监控数据对比
建议准备1-2个深度参与的项目,能说清楚每个技术决策背后的权衡。