1. 面试场景还原:从基础到高阶的Java技术考察
谢飞机坐在腾讯大厦的会议室里,手心微微出汗。对面的面试官推了推眼镜,开始了这场技术交锋:
"先说说Java集合框架中ArrayList和LinkedList的区别?"面试官抛出了第一个问题。
谢飞机调整呼吸答道:"ArrayList基于动态数组,随机访问O(1)但插入删除O(n);LinkedList基于双向链表,插入删除O(1)但随机访问O(n)。实际选择要看业务场景——比如电商购物车频繁增删商品用LinkedList,而需要快速索引的报表数据更适合ArrayList。"
面试官点评:候选人不仅说出了标准答案,还能结合业务场景分析,这种思维值得肯定
面试官接着追问:"那HashMap的负载因子为什么默认是0.75?"谢飞机暗自庆幸昨晚复习过源码:"这是空间和时间成本的折中。0.75时链表转红黑树的概率约0.00000006,resize次数也合理。如果设置0.5,虽然哈希冲突少但空间浪费;1.0又会导致严重哈希碰撞。"
1.1 Spring框架的连环追问
"说说Spring Bean的生命周期?"面试官切换了话题。谢飞机在白板上画出流程图:
- 实例化Bean
- 属性填充(@Autowired)
- 初始化(InitializingBean)
- 使用阶段
- 销毁(DisposableBean)
"特别注意循环依赖问题,"谢飞机补充道,"三级缓存解决的就是构造函数注入时的循环依赖。比如ServiceA依赖ServiceB,Spring会先暴露半成品对象。"
面试官突然抛出陷阱问题:"@Transactional注解在同类方法调用时为什么不生效?"谢飞机快速反应:"因为Spring AOP基于动态代理,内部方法调用不走代理对象。解决方案有三种:1)拆分成不同类 2)手动获取代理对象 3)使用AspectJ编译时织入。"
1.2 微服务架构的实战拷问
"你们项目怎么保证微服务的数据一致性?"面试官进入深水区。谢飞机列举了几种方案:
- 最终一致性:消息队列+本地事件表
- TCC模式:预留/确认/取消
- SAGA长事务
- 分布式事务框架Seata
"我们订单系统用的是RocketMQ事务消息,"谢飞机举例说明,"先发half消息,本地事务成功后再提交二次确认。这里有个坑——消息查证接口要实现幂等,我们用了redis+lua脚本防重复处理。"
2. 高频考点深度剖析:Java核心机制
2.1 JVM内存模型实战理解
面试官在白板上画出一个JVM结构图:"解释下方法区、堆、栈的关系?"谢飞机接过笔标注:
- 方法区:存储类信息(JDK8后是元空间)
- 堆:对象实例和数组
- 栈:局部变量和方法调用
"注意字符串常量池的位置变化,"谢飞机特别指出,"JDK7从方法区移到堆,这样能避免永久代内存溢出。我们线上就遇到过Metaspace爆满的问题,最后通过-XX:MaxMetaspaceSize参数解决。"
2.2 并发编程的魔鬼细节
"写个死锁的例子?"面试官突然要求现场编码。谢飞机迅速写出经典案例:
Object lock1 = new Object(); Object lock2 = new Object(); new Thread(() -> { synchronized(lock1) { Thread.sleep(100); synchronized(lock2) { /* 卡在这里 */ } } }).start(); new Thread(() -> { synchronized(lock2) { Thread.sleep(100); synchronized(lock1) { /* 卡在这里 */ } } }).start();"排查死锁可以用jstack,"谢飞机继续补充,"我们生产环境用Arthas的thread -b命令更直观。预防死锁要遵循:1)固定加锁顺序 2)使用tryLock 3)设置超时时间。"
3. 数据库与缓存的高阶问答
3.1 MySQL索引的优化实践
"说说B+树索引的原理?"面试官问道。谢飞机用笔记本演示:"相比B树,B+树非叶子节点只存key,能放更多索引;数据全在叶子节点且用链表连接,适合范围查询。这就是为什么InnoDB主键推荐自增——避免页分裂。"
面试官追问:"遇到过索引失效的情况吗?"谢飞机苦笑:"太多了!最典型的是:
- 使用!=或not in
- 对索引列做运算
- 隐式类型转换
- 最左前缀缺失
我们有次接口超时,EXPLAIN发现本该走索引的字段因为使用了SUBSTRING()函数导致全表扫描。"
3.2 Redis的持久化策略
"Redis的RDB和AOF怎么选?"面试官继续深挖。谢飞机对比道:
- RDB:二进制快照,恢复快但可能丢数据
- AOF:记录每条命令,更安全但文件大
"我们混合使用,"谢飞机分享实战经验,"主节点开AOF每秒刷盘,从节点用RDB做备份。注意AOF重写会fork子进程,如果内存太大可能阻塞主线程,我们通过控制auto-aof-rewrite-min-size避免频繁重写。"
4. 系统设计能力考察
4.1 秒杀系统架构设计
"如何设计一个百万QPS的秒杀系统?"面试官抛出压轴题。谢飞机在白板上画出分层架构:
前端:
- 静态资源CDN
- 按钮灰度+计数防抖
- 随机丢弃请求
网关层:
- 限流(令牌桶)
- 黑名单过滤
- 请求聚合
服务层:
- 缓存预热
- 库存分段扣减
- 异步下单队列
数据层:
- Redis集群
- 库存扣减Lua脚本
- 最终一致性补偿
"关键点是预扣库存和实际库存分离,"谢飞机强调,"我们用Redis的DECR原子操作扣减预库存,MQ异步处理真实订单。遇到过Redis集群脑裂导致超卖,后来引入ZooKeeper做分布式锁才解决。"
4.2 微服务治理实践
"说说你们怎么管理上百个微服务?"面试官问道。谢飞机展示技术栈:
- 注册中心:Nacos(支持AP/CP切换)
- 配置中心:携程Apollo
- 流量控制:Sentinel
- 链路追踪:SkyWalking
- 服务网格:Istio
"最头疼的是接口幂等问题,"谢飞机分享踩坑经历,"我们给每个请求生成唯一traceId,配合Redis原子操作实现防重。注意Redis集群模式下Lua脚本要用hash tag保证落在同一节点。"
面试最后,面试官突然问:"如果让你设计一个分布式ID生成器,会考虑哪些因素?"谢飞机沉着应答:
- 全局唯一
- 趋势递增
- 高可用
- 低延迟
"常用方案有UUID、雪花算法、Redis原子incr。我们改进过雪花算法——机器ID改用ZooKeeper持久节点分配,避免手动配置;时间回拨时启动备用worker。"