开头自然段(≥200字)
前一阵子去面某家云厂商的中间件岗位,整场面试大概四十分钟,前三分之一都在聊 Spring Boot 的启动机制和自动配置,后三分之一突然切到分布式缓存,最后还让我用一张白纸把 Redis 缓存穿透的应对方案画出来。面完最大的感触是:互联网大厂 Java 面试并不只是八股文背诵,而是拿八股当节点,拿项目经验当连线,看你有没有能力把 Spring Boot 到分布式缓存这条链路完整串起来。你把一个点讲透也许能过一面,但想把 HR 眼里“高并发、高可用”那套逻辑落地,就要从注解背后的源码细节一直聊到缓存和数据库的一致性边界,中间不能断。
这篇文章我就按当时面试的推进顺序,把每一轮对方真正在意的东西、我是怎么答的、以及答完后自己复盘发现的漏洞全部写出来。方向锁定在 Java 基础、Spring Boot、分布式缓存,适合准备大厂 Java 岗的同学参考,也适合已经工作两三年、想系统梳理技术栈的兄弟拿来查漏补缺。
1. 开场硬刚 Spring Boot 自动装配:这里最容易讲成背诵稿
1.1 面试官没有让我“讲启动流程”,而是问“你如何证明启动流程生效了”
常见面试题里问“Spring Boot 启动流程是怎样的”,大多数人都能背出 SpringApplication.run() 后面有准备环境、创建容器、刷新上下文、启动 Web 服务器这些步骤。但那次面试官换了个问法:你项目里引入一个自定义 Starter,Spring Boot 是如何知道要把这个 Starter 里的 Bean 加载进来的?
这个问题看着仍然是自动装配,但考察点完全不同。背答案的人会说有 AutoConfigurationImportSelector,会读 spring.factories 或者 META-INF/spring/...AutoConfiguration.imports 文件,可一旦面试官继续追问“Spring Boot 2.7 和 Spring Boot 3.x 读取自动配置类的机制有什么差异”,或者“你的自定义 Starter 在本地跑得好好的,发布到生产环境为什么不生效”,死的往往很快。
我当时回答的切入点是:自动装配本质上是约定优于配置的工程化方案。Spring Boot 项目里主启动类上的 @SpringBootApplication 由三个注解复合而成:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。真正干活的其实是 @EnableAutoConfiguration,它通过 AutoConfigurationImportSelector 把配置类批量导入容器,而导入名单来自外部化配置文件。在 Spring Boot 2.7 之前是 spring.factories,从 2.7 开始逐步迁移到 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,目的是为了减少对 spring.factories 这个文件的解析压力,也不容易跟其他框架配置冲突。
1.2 顺着“为什么不见效”说到了条件装配与加载顺序
面试官继续追问:那你自定义的自动配置类如果加载不到,最可能是什么原因?我现场想到的实际场景是条件装配失效。
@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty 这些注解决定了自动配置会不会生效。很多 Starter 打包上线后发现 bean 没创建,不是注册逻辑写错,而是 @ConditionalOnClass(name = "com.xxx.SomeClass") 里写的类在运行时不在 classpath 中,条件判断静默失败。这里有个关键点容易忽略:条件装配的类加载探测有一定开销,而且它对 classpath 里存在但不合法、初始化会抛异常的类非常敏感。生产环境常见问题是在依赖里同时引入两个版本的库,结果条件判断类存在但方法签名不匹配,启动直接报 NoSuchMethodError。
我当时还补了一点:自动配置类的执行顺序也很关键。@AutoConfigureBefore、@AutoConfigureAfter 可以声明相对顺序,比如我们自定义的缓存自动配置需要先于 RedisAutoConfiguration 加载,就必须显式声明。不要依赖导入文件里的书写顺序,因为 Spring Boot 会先收集所有候选配置,再按照注解规则和 @Order 做排序。如果不声明顺序,某些 Bean 因为有 @ConditionalOnMissingBean 可能被框架自带的默认配置抢先注册,自定义实现就一直不会被加载。
1.3 从 Spring Boot 启动过程顺手回答了“内嵌 Tomcat 怎么起来的”
自动装配聊完后,面试官把话题引到了部署上。热词里也有“spring boot tomcat 部署”,这基本是必问点。问题很直接:你们项目是打成 jar 包跑还是 war 包丢 Tomcat?为什么?
我项目里绝大多数微服务是 jar 包直接托管的,那我需要解释 jar 包能直接启动的原理。Spring Boot 的可执行 jar 有三层结构:BOOT-INF/classes 放你的业务代码,BOOT-INF/lib 放所有第三方依赖,加上 SpringApplication 的启动器。它通过自定义的 JarLauncher 加载内嵌 Tomcat,而不是依赖操作系统安装的外部 Tomcat。这样带来的工程收益很明显:环境一致性更高、发布单元只有一个文件、跨环境部署时不用再调 Tomcat 的 JVM 参数和数据源连接池。
但如果公司运维体系有要求,必须部署到已有的 Tomcat 上,那就要改成 war 包并把启动类继承 SpringBootServletInitializer 重写 configure 方法。两种方式各有取舍,面试时我建议不要只说“默认 jar”,要主动说明我们为什么拒绝外置容器:外置 Tomcat 版本由运维统一管控,如果项目里引入了需要特定 Servlet 版本的新特性,很容易出现本地正常、测试环境无法启动的尴尬。把版本管理和运维成本挂钩,面试官会认为你确实在生产环境踩过坑,而不是只会看启动日志。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Java 基础高频追问:集合、JVM 和 OOM 体检,不是背答案而是看排查习惯
2.1 HashMap 连环问背后的真实意图
大厂 Java 面试绕不开 HashMap,但二面面试官往往不是直接让你说底层结构,而是从一句“你项目里用 Map 存数据时考虑过线程安全问题吗”开始。当时对方问了三个问题:
- HashMap 在并发 put 时为什么会有环?
- JDK 8 在解决什么问题上引入了红黑树?
- 红黑树退化的临界条件是什么?
HashMap 并发扩容成环这个问题,JDK 8 其实从链表头插法改成了尾插法,环的问题已经基本被解决,但并发丢数据、size 不准确仍然存在,所以真正要答的是 HashMap 为什么不适合多线程场景。大家在准备时不要只背“不安全”,要细说:并发 put 时两个线程可能同时触发 resize,互相覆盖迁移指针,导致数据丢;JDK 8 的尾插法解决了链表成环,但复制的时机会导致新插入的数据被覆盖。
红黑树那组问题,JDK 8 确实做了优化:当一个桶里链表长度超过 8 且数组长度大于等于 64 时,链表转红黑树,把极端哈希冲突下的查询从 O(n) 压到 O(
