1. 面试题设计的底层逻辑
对于1-5年经验的Java开发者而言,面试题的设计需要兼顾技术深度和工程实践的平衡。这个阶段的开发者已经度过了初级的新手期,但尚未达到架构师级别的系统设计能力。因此,题目设置应该聚焦在三个维度:
- Java核心机制的深入理解(JVM、并发、集合等)
- 常用框架的源码级掌握(Spring、MyBatis等)
- 分布式系统的实战经验(缓存、消息队列等)
我在技术面试中常发现,很多候选人能说出HashMap的实现原理,但被追问"为什么JDK8要引入红黑树优化链表"时就语焉不详。这种知其然不知其所以然的情况,正是中级开发者需要突破的瓶颈。
2. JVM深度考察题集
2.1 内存模型实战问题
题目示例:
"服务出现OutOfMemoryError: GC overhead limit exceeded,如何定位和解决?"
考察要点:
- 对G1和CMS垃圾回收器的选择策略
- MAT内存分析工具的实际使用经验
- 内存泄漏与内存溢出的区分能力
参考答案:
首先通过-XX:+HeapDumpOnOutOfMemoryError参数获取堆转储文件,用MAT分析dominant_tree找到内存消耗最大的对象。我曾遇到一个案例:缓存层没有设置TTL导致LocalCache持续增长。解决方案是:
// 错误示例:无限制的缓存 CacheBuilder.newBuilder().build(); // 正确做法:添加大小和过期限制 CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterWrite(10, TimeUnit.MINUTES) .build();2.2 类加载机制陷阱题
题目:
"实现一个热部署的类加载器,需要注意哪些问题?"
踩坑记录:
在自定义类加载器时,容易忽略以下问题:
- 没有正确实现findClass方法导致双亲委派失效
- 忘记维护加载类的缓存造成方法区溢出
- 未处理版本兼容性问题导致ClassCastException
3. 并发编程高阶问题
3.1 AQS实现原理剖析
题目:
"ReentrantLock的公平锁和非公平锁在AQS中的实现差异是什么?"
技术图解:
公平锁: 1. hasQueuedPredecessors()检查队列 2. 存在等待线程时直接入队 非公平锁: 1. 直接尝试CAS获取锁 2. 失败后才进入队列性能对比:
在基准测试中,非公平锁的吞吐量比公平锁高30-40%,但在高竞争场景下可能导致线程饥饿。我们曾在交易系统中遇到因过度使用非公平锁导致的超时问题。
3.2 并发容器实战场景
题目:
"ConcurrentHashMap在JDK7和JDK8中的实现有哪些重大改进?"
演进对比:
| 特性 | JDK7 | JDK8 |
|---|---|---|
| 数据结构 | Segment分段锁 | 数组+链表/红黑树 |
| 并发控制 | ReentrantLock | CAS+synchronized |
| 扩容方式 | 分段扩容 | 协助扩容 |
实战建议:
在Java8+环境中,CHM的size()方法不再需要全局加锁,但频繁调用仍然会影响性能。建议使用mappingCount()方法获取近似值。
4. Spring框架源码级问题
4.1 循环依赖解决之道
题目:
"Spring如何解决构造器注入的循环依赖问题?为什么不行?"
源码解析:
三级缓存的工作流程:
- singletonFactories(三级缓存):存放ObjectFactory
- earlySingletonObjects(二级缓存):存放早期引用
- singletonObjects(一级缓存):存放完整Bean
构造器注入限制:
由于Bean在构造阶段尚未放入缓存,导致无法提前暴露引用。解决方案:
- 改为setter注入
- 使用@Lazy延迟初始化
- 重构代码消除循环依赖
4.2 事务传播机制陷阱
题目:
"在同一个类中,方法A调用方法B,B方法上的@Transactional会生效吗?"
原理揭秘:
由于Spring事务基于AOP实现,自调用会绕过代理机制。解决方案:
- 将方法B拆分到另一个Service
- 通过AopContext获取当前代理(需开启exposeProxy)
((UserService)AopContext.currentProxy()).methodB();5. 分布式系统设计题
5.1 Redis分布式锁优化
题目:
"基于Redis的分布式锁实现有哪些优化方向?"
进阶方案:
- 红锁(RedLock)算法:多节点部署降低单点故障风险
- 看门狗机制:解决业务执行时间超过锁有效期的问题
- 客户端标识:避免误删其他客户端的锁
代码示例:
// 简单实现的问题:非原子操作 if(jedis.get(lockKey).equals(clientId)){ jedis.del(lockKey); // 可能删除别人的锁 } // 优化方案:Lua脚本保证原子性 String script = "if redis.call('get',KEYS[1]) == ARGV[1] then " + "return redis.call('del',KEYS[1]) " + "else return 0 end"; jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(clientId));5.2 消息队列可靠性保障
题目:
"如何保证Kafka消息的Exactly-Once语义?"
技术方案:
- 生产者端:启用幂等发送(enable.idempotence=true)
- 消费者端:事务隔离级别+手动提交offset
- 存储层:配合支持事务的数据库
配置示例:
# 生产者配置 acks=all retries=Integer.MAX_VALUE enable.idempotence=true # 消费者配置 isolation.level=read_committed enable.auto.commit=false6. 系统性能优化实战
6.1 JVM参数调优指南
典型配置:
# G1垃圾回收器优化示例 -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:InitiatingHeapOccupancyPercent=45 -XX:G1ReservePercent=10调优经验:
- 新生代大小建议占堆内存的30-50%
- MetaspaceSize需要根据类加载量调整
- 对于CMS收集器,-XX:CMSInitiatingOccupancyFraction建议设为70
6.2 SQL优化案例分析
题目:
"EXPLAIN显示type=ALL的全表扫描,有哪些优化手段?"
优化路线图:
- 检查WHERE条件字段是否建立索引
- 避免在索引列上使用函数操作
- 注意最左前缀原则
- 考虑使用覆盖索引
反面案例:
-- 错误示例:索引失效 SELECT * FROM orders WHERE DATE(create_time) = '2023-01-01'; -- 优化方案:使用范围查询 SELECT * FROM orders WHERE create_time BETWEEN '2023-01-01 00:00:00' AND '2023-01-01 23:59:59';7. 设计模式高阶应用
7.1 动态代理技术对比
题目:
"JDK动态代理和CGLIB在Spring AOP中如何选择?"
选择策略:
| 维度 | JDK Proxy | CGLIB |
|---|---|---|
| 代理目标 | 接口 | 类 |
| 性能 | 调用快,生成慢 | 生成快,调用稍慢 |
| 依赖 | 内建 | 需引入jar包 |
| 限制 | 不能代理final方法 | 不能代理final类 |
实战建议:
在Spring Boot 2.x中,默认使用CGLIB代理。如需切换:
spring.aop.proxy-target-class=false7.2 策略模式复杂应用
题目:
"如何实现支持运行时动态变更的策略模式?"
解决方案:
- 使用ConcurrentHashMap维护策略映射
- 通过配置中心监听策略变更
- 采用CopyOnWrite机制保证线程安全
代码结构:
// 策略上下文 public class PaymentContext { private static final Map<String, PaymentStrategy> strategies = new ConcurrentHashMap<>(); public static void register(String type, PaymentStrategy strategy) { strategies.put(type, strategy); } public void execute(String type, BigDecimal amount) { strategies.get(type).pay(amount); } }8. 问题排查工具箱
8.1 Arthas高级用法
场景:
"如何在不重启服务的情况下,定位接口性能瓶颈?"
操作流程:
- trace命令追踪方法调用链路
- watch命令观察参数返回值
- profiler命令生成火焰图
典型输出:
[arthas@12345]$ trace com.example.Service * '#cost > 100'8.2 堆外内存泄漏排查
诊断步骤:
- NMT(Native Memory Tracking)开启:
-XX:NativeMemoryTracking=detail - 对比基线报告:
jcmd <pid> VM.native_memory baseline jcmd <pid> VM.native_memory detail.diff - 重点检查DirectByteBuffer和MappedByteBuffer的使用
9. 编码规范与最佳实践
9.1 异常处理原则
黄金法则:
- 永远不要吞异常(空的catch块)
- 受检异常与非受检异常的选择
- 异常包含足够上下文信息
反面教材:
// 错误示例:丢失异常信息 try { parseFile(file); } catch (Exception e) { log.error("parse failed"); } // 正确做法:保留异常链 try { parseFile(file); } catch (IOException e) { throw new BusinessException("文件解析失败: " + file.getName(), e); }9.2 集合使用规范
高频问题:
- Arrays.asList()返回的列表不可变
- subList()与原列表共享存储
- ConcurrentModificationException的预防
防御性编程示例:
// 创建新列表避免共享存储 List<String> safeList = new ArrayList<>(originalList.subList(from, to));10. 系统设计思维训练
10.1 秒杀系统设计要点
关键架构:
- 流量削峰:队列缓冲+异步处理
- 库存预热:Redis预减库存
- 防刷机制:验证码+频率限制
技术组合:
用户层 -> Nginx限流 -> 网关层 -> 消息队列 -> 服务层 -> 缓存集群 -> DB分库10.2 分布式ID生成方案
方案对比:
| 方案 | 优点 | 缺点 |
|---|---|---|
| UUID | 简单 | 无序,索引效率低 |
| 数据库自增 | 递增有序 | 单点瓶颈 |
| Snowflake | 高性能 | 时钟回拨问题 |
| Leaf | 高可用 | 依赖外部存储 |
Snowflake实现要点:
// 64位ID结构 0 | 0000000000 0000000000 0000000000 0000000000 0 | 00000 | 00000 | 000000000000 |- 41位时间戳 -| |- 10位机器ID -| |- 12位序列号 -|