大厂 Java 面试实录:严肃面试官 VS 水货程序员谢飞机,3轮连问后“回家等通知”
大厂 Java 面试实录严肃面试官 VS 水货程序员谢飞机3轮连问后“回家等通知”正文“下一位谢飞机。”会议室门缓缓打开一位穿着格子衫、背着双肩包、头发略显倔强的程序员探进脑袋。“您好面试官我叫谢飞机五年……额三年半……呃反正很多年开发经验主攻 Java 全栈后端架构调优分布式高并发微服务生态闭环。”面试官抬了抬眼镜“坐。简历上写你熟悉 Java 核心、JUC、JVM、SpringCloud、Dubbo、Redis、MySQL、Docker、DDD。那我们开始。”谢飞机正襟危坐“您问我这人最大的优点就是回答问题的时候特别自信。”面试官“希望你的技术和自信成正比。”谢飞机“那可能要让您失望一半了。”第一轮从基础集合到并发看看你是不是‘真会写 Java’问题 1说一下HashMap的底层数据结构以及为什么线程不安全面试官“先来个基础题。HashMap底层是什么”谢飞机“这个我会。HashMap底层是数组加链表加红黑树。数据先通过 hash 找桶位发生冲突就挂链表如果链表太长就转红黑树提升查询效率。”面试官点头“嗯继续为什么线程不安全”谢飞机“因为……多个线程一起 put 的时候大家都很着急一个不小心就把数据覆盖了。以前好像还能形成死循环CPU 原地升天。”面试官“回答还行至少不是背错版本。”谢飞机嘴角上扬“谢谢老师我在集合这块一直比较集合。”问题 2ArrayList和LinkedList的区别业务中你怎么选面试官“那ArrayList和LinkedList呢”谢飞机“ArrayList底层是动态数组查询快随机访问效率高尾部插入也还行LinkedList是双向链表理论上插入删除快。”面试官“为什么说理论上”谢飞机“因为如果你先得遍历半天才能找到位置那也没多快。实际业务里大部分还是用ArrayList缓存友好性能通常更稳。”面试官难得露出一点赞许“不错这个回答比很多只会背八股的人强。”谢飞机小声嘀咕“我主要是背过很多次背出感情了。”问题 3synchronized和ReentrantLock有什么区别面试官“进入并发。synchronized和ReentrantLock区别是什么”谢飞机“呃……都是锁。synchronized是 JVM 帮你管ReentrantLock是 Java 代码自己管。然后ReentrantLock更灵活可以 tryLock可以可中断可以公平锁还能绑定多个 Condition。”面试官“那你平时怎么选”谢飞机“简单同步我就synchronized代码短不容易忘记释放锁复杂并发场景比如超时获取锁、可中断等待、多条件队列我就ReentrantLock。”面试官“可以。”谢飞机挺直腰板“您这句可以对我很重要。”问题 4线程池核心参数有哪些线上为什么不建议用Executors创建线程池面试官“说说线程池。”谢飞机“线程池核心参数有corePoolSize、maximumPoolSize、keepAliveTime、阻塞队列、线程工厂、拒绝策略。”面试官“那为什么不建议直接用Executors”谢飞机“因为……它太贴心了贴心得容易出事。比如有的队列是无界的任务一多容易把内存撑爆有的线程数能开到特别大会把机器打挂。”面试官“还算准确。那如果是订单系统异步发通知你怎么配置线程池”谢飞机沉思两秒“我一般看心情……啊不是看业务峰值、任务类型、响应时延要求、机器核数来定。CPU 密集型线程数接近 CPU 核数IO 密集型可以适当高一些。”面试官“前半句删掉后半句还行。”第二轮结合业务从 Spring 到缓存和数据库面试官翻了翻简历“你做过电商系统那我们就按下单链路问。”谢飞机“好我对下单很熟尤其对没抢到优惠券这件事非常有业务理解。”问题 1Spring 中 Bean 的生命周期大致是什么面试官“如果一个订单服务是 Spring Bean它的生命周期你说一下。”谢飞机“这个……先实例化再属性注入然后如果实现了一些感知接口就回调再执行初始化方法最后就可以被用了。容器关闭时如果配置了销毁方法还会执行销毁。”面试官“那 BeanPostProcessor 在哪”谢飞机“在初始化前后都可以增强 Bean比如 AOP、代理对象生成很多都和它有关系。”面试官轻轻点头“基础还算扎实。”谢飞机“谢谢我在 Bean 生命周期这块活得也挺完整。”问题 2Spring 事务为什么会失效举个下单场景例子。面试官“订单创建、扣库存、写支付流水Spring 事务在哪些情况下会失效”谢飞机“常见有这些方法不是public自调用自己类里直接调自己的事务方法异常被吃掉了没有抛出数据库引擎不支持事务没有被 Spring 管理配置的回滚异常类型不对比如默认对运行时异常回滚。”面试官“如果订单方法里 try-catch 把异常吞了会怎样”谢飞机“事务管理器以为你成功了就给你提交了。结果订单写进去了库存没扣成数据就不一致。”面试官“不错这题答得挺顺。”谢飞机开始膨胀“我在失败经验这块积累非常丰富。”问题 3MyBatis 的#{}和${}有什么区别为什么一般不用${}面试官“继续。MyBatis 里参数占位符区别呢”谢飞机“#{}是预编译占位会转成?由 PreparedStatement 设置参数能防 SQL 注入${}是字符串直接拼接风险比较大。”面试官“那${}完全不能用吗”谢飞机“也不是比如动态表名、排序字段这类场景可能会用但必须严格校验不能让用户随便传。”面试官“这个回答没问题。”谢飞机“我终于在数据库领域像个人了。”问题 4Redis 缓存穿透、击穿、雪崩分别是什么电商商品详情怎么设计面试官“商品详情页访问量很高用 Redis 怎么扛”谢飞机“缓存穿透就是查一个根本不存在的数据请求每次都打到数据库击穿是某个热点 key 失效瞬间大量请求直冲 DB雪崩是大量 key 同时过期或者 Redis 整体挂了。”面试官“那怎么解决”谢飞机“穿透可以缓存空值、布隆过滤器击穿可以热点 key 永不过期或者互斥锁重建缓存雪崩可以给过期时间加随机值、做多级缓存、限流降级、Redis 高可用。”面试官“如果商品价格经常变化呢”谢飞机“那就……价格单独做 key或者通过延时双删、旁路缓存更新、订阅变更消息来保证相对一致性。”面试官“可以说明做过一些实际场景。”谢飞机“也有可能是事故做多了。”问题 5MySQL 索引底层为什么用 B 树面试官“最后一个MySQL 索引为什么常用 B 树不用红黑树或者普通 B 树”谢飞机“因为数据库数据在磁盘上B 树更适合磁盘 IO。它层级低范围查询也方便叶子节点还能形成有序链表。”面试官“普通 B 树为什么差一些”谢飞机“B 树每个节点都存数据范围扫描没 B 树顺红黑树树高也容易更高磁盘访问次数更多。”面试官“还可以。”谢飞机擦了擦额头“老师我觉得第二轮我发挥出了带专985的水平。”面试官“不要给学历系统制造歧义。”第三轮分布式与架构题开始拉开差距面试官合上简历“下面问点复杂的看看你到底是架构师还是‘会改配置的 CRUD 工程师’。”谢飞机咽了口水“老师您尽量温柔一点我的分布式知识比较怕生。”问题 1Dubbo 一次服务调用大致经过哪些过程如果超时你怎么排查面试官“说说 Dubbo 调用链路。”谢飞机“消费者启动时会从注册中心拉取提供者地址做负载均衡选一个服务节点然后通过代理发起远程调用经过序列化、网络传输到服务端执行再把结果返回。”面试官“如果一个查询订单接口频繁超时呢”谢飞机“先看是不是网络抖动、服务端处理慢、数据库慢 SQL、线程池满了、连接池不够、序列化太重、GC 卡顿……反正哪里都可能慢。”面试官“排查步骤呢”谢飞机“呃……先看日志再看监控再看机器再看 JVM再看数据库再问同事。”面试官面无表情“最后一步倒是很多人都会。”问题 2RabbitMQ 如何保证消息不丢如果重复消费怎么办面试官“下单后要发消息通知库存、积分、物流。RabbitMQ 如何保证消息可靠性”谢飞机“这个问题很大我尽量组织一下语言。生产者要保证消息发到 MQ可以用发送确认机制MQ 本身要做持久化比如交换机、队列、消息持久化消费者处理时要手动 ack处理成功再确认。”面试官“如果消费者消费成功了但 ack 前进程挂了呢”谢飞机“那消息会重新投递所以业务要保证幂等。比如根据订单号、消息 ID 做去重或者借助唯一索引、状态机控制避免重复处理。”面试官“那消息积压怎么办”谢飞机“扩容消费者提高并发检查是不是有慢消息、异常重试风暴必要时做消息分级处理。”面试官“这题答得还算像样。”谢飞机“谢谢老师我在 MQ 这块属于半懂不懂里懂得比较多的。”问题 3讲一下 JVM 内存结构、对象创建过程以及一次 Full GC 可能由什么引起面试官“JVM 来了。说。”谢飞机眼神开始飘忽“JVM 内存结构有堆、栈、方法区、程序计数器、本地方法栈。对象一般先在……年轻代里分配如果放不下可能大对象直接进老年代。对象创建要先类加载检查、分配内存、初始化零值、设置对象头、执行构造方法。”面试官“那 Full GC 的常见原因呢”谢飞机“这个……内存不够的时候就会 Full GC。还有 System.gc()还有那个……元空间满了老年代满了反正 GC 不讲武德的时候就会来。”面试官沉默了两秒“你这回答前半段像背过后半段像许愿。”谢飞机尴尬一笑“JVM 比较抽象我一般和它通过日志沟通。”问题 4线程池参数如何根据业务压测结果调优面试官“再来一道多线程和性能相关。线程池调优你怎么做”谢飞机“先区分任务类型CPU 密集还是 IO 密集。然后看压测 TPS、响应时间、队列堆积、线程活跃数、拒绝次数、CPU 使用率、上下文切换这些指标。”面试官“如果线程数一味增加为什么不一定更快”谢飞机“因为线程太多会带来上下文切换开销、锁竞争、CPU 抢占还可能把数据库、Redis、下游接口一起压垮。”面试官“那你会怎么定”谢飞机“先给一个初始值压测观察瓶颈再逐步调 core、max、queue 容量和拒绝策略找到吞吐和延迟的平衡点。”面试官“这题回答还可以。”谢飞机心里又亮了一盏灯。问题 5你理解的 DDD 是什么如果做一个优惠券系统如何拆分领域面试官“最后一道。DDD 怎么理解”谢飞机“DDD 就是……领域驱动设计。核心思想是按业务领域建模不是按数据库表硬拆。会有实体、值对象、聚合、领域服务、应用服务、仓储这些概念。”面试官“那优惠券系统呢”谢飞机“可以拆成券模板、券发放、券领取、券核销、规则校验这些领域……大概。然后用聚合根统一管理一致性。再画一堆图让系统显得很高级。”面试官“前半句还行最后一句很诚实。”谢飞机“因为我见过有些项目业务还没理清先把包名改成 domain、application、infrastructure 了。”面试官终于忍不住笑了一下。面试结束面试官合上电脑“今天先到这里。你的基础题回答还可以部分业务场景也有一定理解但在 JVM、分布式排障、DDD 落地这些复杂问题上深度还不够回答比较发散。”谢飞机坐得笔直“老师我这个人优点就是可塑性强缺点就是现在还没塑好。”面试官“嗯你先回去等通知吧。”谢飞机起身深深鞠躬“好的老师希望这个通知不是‘感谢参与’。”走出会议室后谢飞机掏出手机记下两句话基础八股还能救命JVM 不能再靠玄学了。面试问题标准答案详解下面对上面所有问题做系统梳理帮助小白真正学明白。1. HashMap 底层数据结构以及为什么线程不安全底层结构JDK 1.8 中HashMap底层是数组桶链表红黑树数据插入时先根据 key 的 hash 值计算桶下标如果该桶为空直接放入如果冲突先挂到链表当链表长度超过阈值且数组容量达到一定条件后链表会树化为红黑树。为什么线程不安全在多线程下多线程 put 可能导致数据覆盖扩容 resize 时可能产生数据丢失或结构异常JDK 1.7 中头插法在并发扩容下可能形成链表死循环多线程读写没有加锁数据可见性和原子性都无法保证。解决方案并发场景优先使用ConcurrentHashMap或者外部加锁控制2. ArrayList 和 LinkedList 的区别ArrayList底层动态数组优点支持随机访问get(index)快尾部插入性能较好内存连续缓存命中率高缺点中间插入、删除需要移动元素扩容会有数组拷贝成本LinkedList底层双向链表优点已知节点位置时插入删除较方便缺点随机访问慢要遍历节点额外保存前驱后继指针内存开销更大CPU 缓存不友好实际怎么选绝大多数业务场景优先ArrayList。除非明确需要频繁在链表特定位置插入删除否则一般不用LinkedList。3. synchronized 和 ReentrantLock 的区别synchronizedJava 内置关键字使用简单自动加锁和释放锁适合简单同步场景早期性能一般后来经过偏向锁、轻量级锁等优化后性能不错ReentrantLockJUC 包中的显示锁需要手动lock()和unlock()功能更丰富支持可中断锁lockInterruptibly()支持超时获取锁tryLock()支持公平锁/非公平锁支持多个条件队列Condition如何选择简单同步优先synchronized高级并发控制用ReentrantLock4. 线程池核心参数以及为什么不建议用 ExecutorsThreadPoolExecutor 核心参数corePoolSize核心线程数maximumPoolSize最大线程数keepAliveTime非核心线程空闲存活时间workQueue任务阻塞队列threadFactory线程工厂RejectedExecutionHandler拒绝策略任务执行流程当前线程数 core直接创建核心线程执行否则任务进入队列队列满了且线程数 max再创建非核心线程如果线程数达到 max 且队列也满则触发拒绝策略为什么不建议用 ExecutorsExecutors某些工厂方法隐藏了风险newFixedThreadPool()使用无界队列任务过多可能 OOMnewCachedThreadPool()最大线程数接近无限可能创建过多线程newSingleThreadExecutor()也是无界队列风险正确做法直接使用ThreadPoolExecutor明确指定参数。5. Spring Bean 生命周期大致流程实例化 Bean属性注入执行 Aware 接口回调BeanPostProcessor前置处理执行初始化方法InitializingBean.afterPropertiesSet()自定义 init-methodBeanPostProcessor后置处理Bean 可被使用容器关闭时执行销毁方法DisposableBean.destroy()自定义 destroy-method作用理解生命周期有助于理解AOP 代理生成Bean 初始化扩展自定义框架能力实现6. Spring 事务为什么会失效常见原因自调用失效同类内部方法直接调用绕过代理方法不是 public默认代理可能不生效异常被吞掉事务感知不到异常导致提交抛出的不是配置回滚的异常类型对象不是 Spring 容器管理的 Bean数据库引擎不支持事务如 MyISAM传播行为使用不当实战建议事务方法放到独立 Bean 中不要吞异常明确回滚规则rollbackFor Exception.class7. MyBatis 的 #{} 和 ${}#{}预编译参数占位符生成 SQL 时用?自动处理参数类型可以防止 SQL 注入${}字符串直接拼接不会预编译存在 SQL 注入风险什么时候会用 ${}动态表名动态排序字段但必须做白名单校验绝不能直接接收用户输入拼接。8. Redis 缓存穿透、击穿、雪崩缓存穿透查询不存在的数据缓存和数据库都没有每次都打到数据库。解决缓存空对象布隆过滤器接口参数校验缓存击穿热点 key 在失效瞬间大量请求同时访问数据库。解决热点数据永不过期互斥锁/分布式锁重建缓存后台异步刷新缓存雪崩大量 key 同时过期或 Redis 整体不可用导致请求大量打到 DB。解决过期时间加随机值Redis 主从、哨兵、集群多级缓存限流、降级、熔断9. MySQL 索引为什么用 B 树原因一降低磁盘 IO 次数B 树是多叉平衡树一个节点能放很多 key树高低查找时磁盘 IO 更少。原因二范围查询更高效B 树的数据都在叶子节点并且叶子节点之间有链表连接非常适合范围扫描。原因三更稳定B 树非叶子节点只存索引不存数据所以每个节点能容纳更多索引项。为什么不是红黑树红黑树是二叉树树高更高磁盘 IO 更频繁不适合数据库索引。为什么不是普通 B 树B 树的数据分散在各层不如 B 树适合范围查找和顺序扫描。10. Dubbo 一次调用过程以及超时排查思路调用过程服务提供者启动并注册到注册中心消费者启动从注册中心订阅服务地址消费者通过负载均衡选择一个提供者通过动态代理发起远程调用请求进行序列化并通过网络发送服务端反序列化、执行业务逻辑结果返回给客户端超时排查思路可以按链路逐层排查客户端超时时间配置是否合理网络层网络抖动、丢包、连接数是否异常服务端线程池是否满载、队列堆积业务逻辑是否存在耗时计算数据库慢 SQL、锁等待、连接池不足缓存/下游依赖Redis、MQ、第三方接口是否慢JVM是否频繁 GCStop-The-World 时间过长机器资源CPU、内存、磁盘、负载是否异常常用手段查看调用链监控看服务日志看线程 dump看 GC 日志分析慢 SQL看 Prometheus/Grafana 等监控11. RabbitMQ 如何保证消息不丢以及如何处理重复消费如何保证消息不丢从三个阶段看1生产者到 MQ开启publisher confirm必要时结合return机制处理路由失败发送失败要重试或记录补偿2MQ 自身存储队列持久化交换机持久化消息持久化集群/镜像队列提高可用性3MQ 到消费者消费者手动 ack处理成功后再确认失败可重试、转死信队列重复消费怎么处理因为网络抖动、消费者重启、ack 失败等都可能导致重复投递所以业务必须做幂等。幂等方案唯一消息 ID 去重表Redis setnx 去重数据库唯一索引根据业务状态判断是否已处理12. JVM 内存结构、对象创建过程、Full GC 原因JVM 运行时数据区程序计数器记录当前线程执行字节码的位置虚拟机栈方法调用时的栈帧本地方法栈本地方法服务堆存放对象实例是垃圾回收的主要区域方法区/元空间存放类元数据、常量、静态变量等对象创建过程类加载检查类是否已加载分配内存在堆上给对象分配空间初始化零值成员变量先赋默认值设置对象头如 hash、GC 分代信息等执行init构造方法Full GC 常见触发原因老年代空间不足晋升失败大对象直接进入老年代导致空间紧张System.gc()显式触发元空间不足CMS/G1 等垃圾收集过程中的特定回收条件触发如何排查看 GC 日志看堆内存分配情况导出 heap dump 分析对象占用排查内存泄漏、对象生命周期过长问题13. 线程池如何调优调优步骤明确业务类型CPU 密集 / IO 密集估算并发量和任务耗时通过压测观察指标TPSRT活跃线程数队列长度拒绝次数CPU 使用率上下文切换调整参数corePoolSizemaximumPoolSizequeueCapacitykeepAliveTime拒绝策略结合下游承载能力一起看不能只盯线程池本身为什么线程越多不一定越快上下文切换开销变大锁竞争加剧CPU 被打满下游数据库/缓存被压垮内存消耗增大原则找到系统吞吐、延迟、资源消耗三者的平衡点。14. DDD 是什么优惠券系统如何拆分领域DDD 核心思想DDD领域驱动设计强调软件设计要围绕核心业务领域展开技术模型要贴近业务语言通过建模提升复杂业务的可维护性常见概念实体 Entity有唯一标识如优惠券实例值对象 Value Object无唯一标识如有效期规则聚合 Aggregate一组具有一致性边界的对象集合聚合根 Aggregate Root聚合对外唯一入口领域服务 Domain Service不适合放到实体中的领域逻辑应用服务 Application Service负责编排流程仓储 Repository负责持久化访问优惠券系统拆分示例可以拆成以下子域券模板领域定义券规则、门槛、折扣方式、有效期发券领域向用户发放优惠券领券领域用户主动领取核销领域下单时校验并使用优惠券规则引擎/校验领域校验是否满足使用条件为什么这样拆因为优惠券系统的核心不是“几张表”而是券怎么定义谁能领什么时候能用使用时怎样保证一致性如何防止超发、重复领取、重复核销DDD 适合复杂业务不是简单把包名改成domain就算落地了。15. 补充Linux、Docker、设计模式面试常见延伸点虽然故事里没逐个展开但真实面试中这些也常问。Linux 常见问题top看系统负载ps -ef | grep查进程netstat -tunlp/ss -lntp查端口tail -f实时看日志grep、awk、sed日志分析df -h看磁盘空间free -m看内存Docker 常见问题镜像是静态模板容器是运行实例常用命令docker psdocker imagesdocker logsdocker exec -it核心价值环境一致、快速部署、资源隔离设计模式常见问题单例模式全局唯一实例工厂模式封装对象创建策略模式多种算法/规则可替换模板方法模式定义流程骨架子类实现差异代理模式增强目标对象能力Spring AOP 常见责任链模式请求沿链路逐步处理如审批流、过滤器总结这场面试里谢飞机给我们上了很生动的一课基础题一定要扎实集合、并发、Spring、缓存、数据库索引都是高频题复杂题不能只会背结论要能结合业务讲原理、讲排查、讲取舍分布式系统重点在可靠性、性能、幂等、一致性、可观测性JVM 和线程池调优是区分初中高级工程师的重要分水岭DDD不是换目录结构而是面向复杂业务建模。如果你也正在准备 Java 面试不妨把这篇文章里的问题逐个吃透。别让自己在面试现场像谢飞机一样基础题满面春风复杂题开始随风飘零。