刚面完这一轮Java岗位的密集面试,问题还热乎,赶紧把记忆里的细节倒出来。今年的面试风向和两三年以前差别非常大:Spring Boot和微服务依然是绝对主力,但考法已经从“会不会用某个注解”升级到“能不能把一个后端系统真正维护明白”;与此同时,AI技术几乎成了二三面的隐藏题,从基础的JVM调优、Redis队列消费,再到大模型接口怎么在Java服务里被封装调用,大厂面试官都会盯着实际场景往下追问。
这篇实录就当给准备后端岗位的同学做个参照。我不会逐题给所谓标准答案,而是按我被问到的大致顺序,把考察点、现场回答思路、以及事后复盘发现的问题串起来。主线基本就是Java基础、Spring Boot、微服务、AI技术四条,中间穿插真实项目踩坑和临场应对技巧。文章不会太长篇大论讲理论,尽量做到每一段都能直接搬到你的复习清单里。
1. 面试准备阶段:这轮考察风向明显变了
1.1 考题变化:从背八股到看真实系统能力
过去准备大厂Java面试,刷题基本围绕synchronized和volatile区别、Spring Bean生命周期、MySQL索引失效这类“标准八股”。现在这些仍然会考,但比例明显收缩,取而代之的是大量场景题和落地题。我在面试中遇到的典型变化可以整理成一张对照表:
| 考察方向 | 以前偏爱的问法 | 现在更常见的问法 |
|---|---|---|
| Java并发 | synchronized和ReentrantLock的区别 | 线程池核心线程数怎么定?队列满了以后任务执行的先后顺序? |
| Spring Boot | 常用注解有哪些 | 自动装配是怎么加载的?有没有自己写过Starter? |
| 微服务 | 什么是微服务,有哪些组件 | 服务怎么拆分边界?消息积压以后怎么扩容?事务怎么保证最终一致性? |
| 监控运维 | 基本不问 | Actuator和Micrometer怎么配合Prometheus暴露指标? |
| AI技术 | 完全不问 | Java后端怎么调大模型接口?怎么做RAG检索增强? |
这个变化背后是有原因的。现在很多业务系统的基础功能做起来不难,难的是在流量、数据量、团队协作都上来以后,系统还能不能稳定运行、快速定位问题。面试官真正要考察的不是你会不会写CRUD,而是你面对一个具体问题时的思考路径和工程判断。
1.2 面试轮次结构和每一关筛什么
大厂Java岗位的流程基本稳定在三到五轮,每一轮考察重心不一样。我这次遇到的轮次大致如下:
- 第一轮通常是技术面,主要考Java基础和算法,有时候会穿插一些Spring Boot原理题。这一关卡得最狠的其实是基础扎不扎实,很多人挂在集合源码细节和并发边界问题上。
- 第二轮是框架与项目深挖,Spring Boot、微服务架构会集中出现,如果简历写了高并发、分布式项目,面试官会顺着你的描述一层层往下问。
- 第三轮进入系统设计或全局架构面,这时候微服务拆分、消息队列、缓存一致性就会变成主角,有时候还会抛一个“如果某天接口突然变慢你怎么排查”的开放题。
- 如果级别够高,还会有交叉面和Leader面,这一轮除了技术,还会聊协作、业务理解、AI技术怎么落到具体产品里。
每个轮次的关注点不一样,准备方式也就不同。基础轮适合用“知识点+小案例”去验证,项目轮一定要准备一个自己能讲清楚的数据流和故障案例。
1.3 简历上最容易暴露问题的一个点
有位面试官在开场时没有直接问技术,而是指着我简历上的一个项目说:“你写了自己负责的系统从单体拆成微服务,请画出当时的架构图,并且说明每个服务拆出来的原因。”这一下就暴露问题了——很多人项目描述里喜欢堆技术名词,但真要解释为什么要拆、边界怎么划、拆完以后分布式事务怎么处理,就支支吾吾。
所以准备阶段第一件事不是背题,而是把你写在简历上的每个项目用“业务背景、你的职责、核心难点、量化结果”四句话过一遍。尤其要准备好一个“如果某天线上出了XX故障,你是怎么排查和解决的”案例,面试官对这一类问题的兴趣远远大于你的项目功能列表。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Java核心考点:并发、内存与代码功底
2.1 线程池那条最经典的追问链
几乎每一场技术面试都会遇到线程池。面试官很喜欢从一个简单问题开始,然后连环追问。我遇到的完整链路是这样的:
面试官:Executors工具类里有哪几种常用线程池?
我:FixedThreadPool、CachedThreadPool、SingleThreadExecutor、ScheduledThreadPool。
面试官:这些线程池的队列分别是什么?哪种在生产环境里最危险?
我:FixedThreadPool和SingleThreadExecutor用的是无界LinkedBlockingQueue,任务无限堆积可能导致内存溢出的风险。
面试官:既然这样,你自己创建线程池会怎么设置参数?假如核心线程2、最大线程4、队列容量100,同时来150个任务,执行顺序是怎样的?
这个问题回答容易踩坑。很多人直觉是“核心线程满了就创建新线程到最大线程数”,实际上线程池在核心线程满之后会先尝试往队列里放任务,队列也满了才会创建非核心线程,当线程数到达最大线程数以后再有新任务才会走拒绝策略。所以上面那个场景的顺序是:前2个任务占用核心线程,接下来100个任务进入队列,再接下来4个任务创建新线程执行(实际会从第103个开始创建非核心线程),最后剩下的44个任务触发拒绝策略。
线程池参数也没有一个固定公式。CPU密集型任务可以按CPU核数+1来设置,IO密集型可以适当调大线程数,更准确的办法是根据“任务耗时中等待IO的比例”折算。现场我给的估算公式是:线程数 = CPU核数 * (1 + 等待时间 / 计算时间)。如果系统里有依赖第三方接口的调用,等待时间可能远大于计算时间,线程数就往大了调。
还要注意一个连环坑:线程池里如果继续往同一个线程池提交任务,可能造成线程池“饥饿”。原因是所有线程都在等待子任务执行,而子任务又排在队列里没有空闲线程去消费。这种情况在异步编排、递归拆分任务时要格外小心。
2.2 一次真实的OOM排查:不只是-Xmx调大就完事
搜索词里出现过“java: outofmemoryerror: insufficient memory”,很多人以为OutOfMemoryError就是把堆调大。实际上面试官真正想听的是你能不能定位到是哪块内存出了问题。
比较典型的排查路径是这样的:
- 先看错误日志是哪种OOM。Java堆内存溢出通常是“Java heap space”,Metaspace溢出是“Metaspace”,还有一个常见的是“unable to create new native thread”,说明线程数已经达到操作系统限制。
- 如果日志没有打出来,进程直接被系统杀掉,要考虑是不是发生在容器内部。JVM默认会读取宿主机内存来设置最大堆,你明明在Docker里限制了内存512M,JVM却可能按照宿主机几十G内存去分配,最后被操作系统OOM Killer杀掉。解决办法是显式设置
-Xmx,或者加上-XX:+UseContainerSupport并让JVM正确识别容器限额。 - 抓证据、不是猜原因。启动参数加上
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logs,下次OOM自动落盘,然后用MAT或JProfiler分析Dominator Tree,看哪些对象占用了大部分堆空间。
我实际排查过的一个案例是短信发送服务每天下午固定时段卡死,日志里出现OutOfMemoryError。Heap dump后看到大量byte[]被一个定时任务线程持有,原因是那段时间合作方接口变慢,HTTP客户端默认的读超时时间又没设,导致所有响应线程都堵在等待数据上,响应对象在内存里越积越多。最后解决方案不是简单加内存,而是给HTTP客户端设了连接超时和读超时,并增加了线程池隔离。
2.3 Lambda、Stream与手写排序的隐藏考点
Java 8以后的语法在面试里单独出题不多,但会以算法题和代码阅读题的方式出现。我遇到一题是让用Stream实现“按分数降序排序、同分按学号升序,取前10个学生”,回答里要自然写出filter、sorted、limit、collect(Collectors.toList())的组合。
另一个容易忽略的小点是方法引用和Lambda捕获变量的限制。Lambda表达式里不能修改外部非final变量,本质上是为了线程安全约束。还有并行流parallelStream(),很多人看到数据量大就喜欢用,但并行流默认使用公共的ForkJoinPool,线程数是CPU核数减一,一旦某个任务阻塞,会拖累同一个JVM里其他使用并行流的业务。这种坑在运行一段时间后会非常隐蔽。
手写排序题也经常考。面试官可能先让你写一个“你认为最简单的排序算法”,写冒泡之后他一定会追问“还能优化吗”。此时只要加一个是否发生过交换的标记就能让最好情况变成O(n):
java复制public static void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
boolean swapped = false;
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
swapped = true;
}
}
if (!swapped) {
break;
}
}
}
如果追问“为什么在真实场景不用冒泡”,可以答:冒泡平均时间复杂度是O(n²),在数据量大时性能差,而Java自带的Arrays.sort对基本类型用双轴快排,对对象数组用TimSort,能利用数据的有序性。到这里,一个简单的排序题就能带出算法复杂度、工程选型和JDK底层设计三条信息。
3. Spring Boot核心细节:从自动装配到生产可观测
3.1 自动装配和自定义Starter的底层逻辑
Spring Boot的自动装配是面试频率极高的考点。第一层问题通常问“Spring Boot为什么能省掉那么多XML配置”,这背后的关键是@EnableAutoConfiguration结合AutoConfiguration.imports文件,把符合条件的配置类加载进来。
真正能拉开差距的是第二层追问“你怎么自己写一个Starter”。回答思路要清晰:
- 在
resources/META-INF/spring目录下创建org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,文件里写上你的自动配置类全限定名。 - 自动配置类上用
@AutoConfiguration,配合@ConditionalOnClass、@ConditionalOnProperty等条件注解,保证只有类路径有对应依赖或用户开启了相关配置时才生效。 - 用
@ConfigurationProperties绑定外部配置项,再通过@EnableConfigurationProperties注册到容器里。
为什么要这么设计?因为第三方库不能替用户做决定,只有当用户显式引入了依赖、并且在配置文件里出现了开关项时,才应该自动装配。如果框架没有条件注解,导致每个项目启动都加载一堆无用Bean,轻则启动变慢,重则出现Bean冲突,这在实际工程里是无法接受的。
3.2 Actuator加Micrometer:让Spring Boot可观测
现在的大厂面试中,“微服务架构图”已经不够看了,“怎么监控起来”才是更现实的问题。Spring Boot Actuator负责暴露端点,Micrometer负责桥接各类监控系统,两者是现在可观测性方案里的标准组合。
我面试里被问到一个很实际的问题:“团队现在有几十个微服务,如何判断某个服务是不是快要扛不住了?”我的回答是:不能光看CPU和内存,要看线程池活跃度、Tomcat最大线程使用率、接口P99耗时、数据库连接池占用、JVM GC频率等业务向指标。Actuator默认暴露的/actuator/health只能做存活探针,要接Prometheus需要把依赖加上:
xml复制<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
配置文件里再开启Prometheus端点:
properties复制management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.metrics.tags.application=${spring.application.name}
如果只是有个监控面板还不够,得把自定义业务指标也暴露出来。比如订单创建数量、支付成功数量可以定义成Counter或Gauge注入到MeterRegistry里:
java复制@Service
public class OrderMetricsService {
private final Counter orderCreatedCounter;
public OrderMetricsService(MeterRegistry meterRegistry) {
this.orderCreatedCounter = Counter.builder("order.created.total")
.description("累计创建订单数")
.register(meterRegistry);
}
public void recordOrderCreated() {
orderCreatedCounter.increment();
}
}
这套东西在面试里能连续展开好几个点,比如Counter和Gauge的区别、Timer适合记录什么、如何按接口维度聚合数据。答好这些,面试官会认为你有真实的生产环境运维经验,而不只是会写接口。
3.3 Redis Stream消费消息,细节一个都不能漏
“spring boot redis stream 如何拉取队列消息”是不少后端岗位现场会冒出来的实战题。现在Redis Stream被大量用做轻量级队列,但对于它的消费机制,很多人只停留在“用RedisTemplate去读消息”的层面。
在Spring Boot中,更推荐的用法是用StreamMessageListenerContainer做自动消费。你需要先创建容器配置,指定轮询超时时间和反序列化器,再往容器里注册一个StreamListener。这个监听器收到消息后需要手动调用ack确认消费成功,这和Redis List类型的BRPOP完全不同,Stream多了一个“消费者组确认”的概念。
关键点有几个:
- 消费者组要提前创建,
XGROUP CREATE stream-msg group-1 0,否则监听容器启动后会报错。 - 监听器里要做业务异常捕获。如果处理失败不
ack,消息会进入Pending列表,后续仍然可以取到;但如果一直失败不处理,Pending列表会越来越大,造成消息越积越多。 - 比较好的实践是把失败消息单独扔进一个死信Stream,同时记录重试次数,超过阈值就转人工。
有一次面试官追问“消息重复消费怎么解决”,这是Redis Stream最常见的问题。因为消费者在业务处理完成以后才ack,如果处理完成但还来不及ack,消费者挂了,这条消息就会被其他消费者重新拉取。解决办法只能在消费端做幂等,比如用全局唯一业务主键去重,或者维护一张处理记录表。这个思路和个人项目经验直接相关,如果简历里写了“用Redis Stream替换旧的轮询任务”,这题必须准备到位。
3.4 外部消息接入与事务边界
Spring Boot服务里少不了对接微信服务号这类第三方平台。比如微信服务号开发者对接时,用户关注、取关事件会通过回调方式推送到你配置的URL,Java侧要做的就是提供两个接口:
- GET接口做签名验证,把timestamp、nonce、signature按字典序排序后做SHA1,和微信传来的signature对比,一致才返回随机字符串。
- POST接口接收XML或JSON格式事件消息,解析后根据MsgType、Event等字段分发到不同业务处理器。
这段工程经验可以抽象成一个更通用的结论:凡是外部平台回调,都必须先验签、再解密、再做消息去重。第三方回调一般有重试机制,你不去重就会重复发券、重复加积分。另外回调响应往往有超时要求,业务处理不要在主线程同步执行,先返回成功,再丢进线程池或MQ慢慢处理。这样做之后“微信服务号关注监听接口怎么设置”这类问题就完全是送分题了。Spring Boot部署相关的主题也是类似套路,现在的项目大多是独立可执行jar,命令行本地跑就是mvn spring-boot:run,生产环境就是java -jar app.jar --spring.profiles.active=prod,如果真要部署到外部Tomcat,则需要打成war包并继承SpringBootServletInitializer。真正要关心的是启动脚本里有没有设置JVM参数和优雅停机,而不是纠结用哪种部署方式。
4. 微服务架构:可落地才是硬道理
4.1 服务拆分先画图,画图之前先理清边界
面试官让“画微服务架构图”时,不是看你图画得好看,而是看你能不能边画边说明白每个组件的职责和流量走向。一张能自圆其说的基础图大概是这样的:
- 最外层Nginx或SLB做流量入口,把HTTP请求转发到API网关;
- 网关层统一处理鉴权、限流、灰度路由,把请求分发到下游业务服务;
- 业务服务按领域划分,例如用户服务、订单服务、支付服务、库存服务;
- 服务之间同步调用走OpenFeign,异步解耦走MQ;
- 注册中心和配置中心用Nacos,所有服务启动时都来注册;
- 数据层按服务拆分数据库,缓存Redis、搜索Elasticsearch、对象存储MinIO独立部署;
- 链路追踪和监控组件贯穿全链路。
拆服务最大的难点不是技术,而是边界划分。我见过很多团队把单个用户表拆出一个“用户微服务”,剩下订单表服务再直接查用户库,结果又搞出一堆跨库join。合理的做法是按业务能力拆,一个服务拥有自己的数据,其他服务只能通过接口访问,不能直接访问它的数据库。如果你的系统没有明确的独立部署和独立扩展需求,不要硬上微服务,模块化单体加领域边界可能更适合团队现状。
4.2 注册中心、网关、熔断怎么答不会跑偏
微服务里Nacos和Eureka的区别是高频题。Eureka是纯粹的服务注册中心,AP模型,保证可用性但可能读到过期服务列表;Nacos除了服务发现还能当配置中心用,同时支持AP和CP两种模式,默认是AP。如果读到的列表里有已经宕机的实例,客户端需要靠重试、负载均衡策略来规避。
网关环节通常会被深挖。Spring Cloud Gateway的核心概念就是路由Route、断言Predicate、过滤器Filter。统一鉴权这样实现:网关里有一个GlobalFilter,从请求头里取Token,解析后把userId放进自定义Header,再转发给下游服务。这里有个安全细节:下游服务不能直接信任前端传的userId,要把网关设置的Header视为唯一可信来源,并且在网关上把外部传入的同名Header清掉,防止伪造。
如果下游服务发生故障怎么办?这时候就需要熔断降级。常用的方案有Sentinel和Resilience4j。面试时不要说“我用熔断就是加个注解”,而是要把降级规则、触发条件、降级后的兜底数据来源讲清楚。比如查询用户详情时,如果调用积分服务失败,可以先返回缓存里的积分快照,在响应里标记“积分数据有延迟”,而不是直接给前端报错。
4.3 分布式事务别开口就背两阶段提交
很多候选人一聊到分布式事务就开始背2PC、3PC、TCC,但面试官真正关心的是你会不会根据场景做取舍。互联网最常用的其实是最终一致性方案。
我现场给过一个下单扣库存的场景:
- 订单服务在自己本地数据库里同时写入订单记录和一条“待发送扣减消息”,这两个操作在同一个本地事务里完成;
- 本地事务提交后,通过定时任务把消息表里状态为“待发送”的记录丢到MQ,发送成功后把状态改成“已发送”;
- 库存服务消费MQ消息,执行库存扣减,扣减成功后调用订单服务回写状态;
- 如果库存服务处理失败,MQ会自动重试,一直失败就进入死信队列转人工处理;
- 下游消费必须做幂等,防止同一消息被重复投递后多次扣库存。
这套方案不依赖强一致的分布式锁,也不引入额外的全局事务协调器,维护成本低。如果业务对一致性要求很高,比如账户余额变动,可以用TCC或者Seata的AT模式,但也要权衡它的性能损耗。在面试里说清楚“什么时候用最终一致性、什么时候用强一致”,比单纯背一套方案得分高得多。
4.4 Activiti工作流在微服务里的正确使用方式
Java后端做审批、工单、合同这类业务经常遇到Activiti,而且如果把它硬塞进一个普通微服务里,会带来巨大的表结构和事务耦合问题。比较合理的做法是独立部署成一个“流程引擎服务”,业务系统通过Feign或MQ来启动流程、完成任务、查询历史。
Activiti真正容易踩坑的是“自定义查询”。Activiti默认的ACT_*表不是业务查询接口,直接去join ACT_HI_TASK_INST或ACT_RU_TASK容易写出各种不稳定SQL,而且随着版本升级字段可能变化。更可靠的做法是维护业务自己的任务表,比如biz_leave里存业务主键、审批人、状态,再关联一个PROC_INST_ID_,查询时优先查业务表,需要流程详情再去调Activiti的HistoryService。如果Activiti还要集成自定义表单,那最好表单数据也落到业务库,流程引擎只保存一个JSON快照,不要指望流程引擎帮你解决业务数据的复杂性。
另外Activiti在微服务环境下做分布式部署也有坑。多实例部署时如果集群节点没有配置好对同一个数据库和同一个锁机制的访问,容易出现重复任务拾取,所以流程引擎服务一般建议保持单实例或采用Activiti自带的集群锁方案,不要简单地在多个节点上无脑部署。
4.5 MinIO这类替代组件怎么评估和落地
对象存储也是Java后端的热点话题。MinIO是兼容S3协议的自托管开源方案,很多团队拿它做公有云对象存储的替换方案。Java侧接入方式很简单,引入MinIO SDK以后,用MinioClient上传下载文件即可。但选型层面有两个容易被忽略的点:
第一,不要让你的业务代码直接依赖某个特定存储SDK,而是抽象一个ObjectStorageService接口,里面有putObject、getObject、removeObject、getPresignedUrl这些方法,再分别实现MinIO、阿里云OSS或本地磁盘实现。这样后期切换存储后端只需要改配置,不需要把业务代码翻个底朝天。
第二,MinIO虽然是开源方案,但部署运维成本要算进来。自建对象存储意味着你要自己考虑数据备份、版本控制、跨机房容灾、小文件性能优化。面试时讲到类似“替换方案”,不要只吹功能,要能说清楚“替换它需要付出什么代价、你做了哪些配置来保证可靠性”,这才是面试官想听到的工程思维。
5. 面试题里的AI技术:Java工程师怎么接得住
5.1 先分清模型训练、微调和应用集成
最近的面试里,AI技术相关内容出现的频率高到让人没法无视。许多Java后端候选人一看AI题就有点慌,觉得那是算法工程师的领域。实际上面试官问Java工程师的AI问题,重点从来不是要你手写Transformer,而是想确认你有没有能力把大模型能力安全稳定地集成到现有业务系统里。
你需要分清楚三件事:
- 模型训练与微调:这是算法团队的事,你需要了解基本过程,但不用深入。
- Prompt工程和模型调用:这是后端要解决的事,要把系统Prompt、用户输入、上下文组织好,并发给模型服务。
- RAG检索增强:这是现在Java后端最需要关注的,知识库内容先切片、向量化,用户提问时先检索相关内容,再组装成Prompt给模型,能显著减少大模型“信口开河”。
面试官如果问“你了解AI Agent吗”,最简单的表达是:Agent就是在LLM能力之上,让它根据用户目标自己决定调用哪个工具、执行哪几步动作,并不断根据结果调整计划。Java后端在这里要提供工具能力的API,比如查天气、查库存、下单,每个工具都要有清晰的入参出参定义和权限控制。
5.2 Java服务调用大模型接口的最小可运行方案
Java里调用大模型的HTTP接口并不复杂。可以用Spring 6的RestClient或者WebClient发起请求,核心是把业务里的Prompt组装请求体、发送给模型服务、解析返回内容。需要特别关注的几个问题是:超时时间、流式输出和重试策略。
一个简化的调用层长这样:
java复制@Component
public class LlmClient {
private final RestClient restClient;
public LlmClient(RestClient.Builder builder, LlmProperties props) {
this.restClient = builder
.baseUrl(props.getBaseUrl())
.defaultHeader("Authorization", "Bearer " + props.getApiKey())
.build();
}
public String chat(String systemPrompt, String userMessage) {
Map<String, Object> payload = new HashMap<>();
payload.put("model", "your-model");
payload.put("messages", List.of(
Map.of("role", "system", "content", systemPrompt),
Map.of("role", "user", "content", userMessage)
));
Map result = restClient.post()
.uri("/chat/completions")
.body(payload)
.retrieve()
.body(Map.class);
// 这里解析result里的choices[0].message.content
return parseContent(result);
}
}
在真实项目里,调用大模型绝对不是同步等返回这么简单。模型推理速度比普通HTTP接口慢很多,一次问答可能要几秒到几十秒。如果前端在等,就要用SSE流式返回,Java后端通过WebClient或Spring MVC的异步支持,把模型的增量输出实时推给前端;不要用同步调用把Tomcat线程池占满。如果业务不需要实时性,应该把请求丢进MQ异步处理,处理完成后通过消息通知或者轮询接口取结果。
还有一个极其重要的点:模型的输出不可控,必须加降级。如果模型服务超时、返回内容格式不对、或者当前并发已经导致大量排队,后端要有直接返回兜底文案或走规则逻辑的能力。这一条在面试里反复被提到,也是大厂评判你有没有把AI真正当工程来做的重要指标。
5.3 RAG知识库:Java后端的核心戏份
如果简历里写了AI项目,那RAG基本是必问题。RAG的完整链路是:文档加载、内容解析、文本切分、向量化、向量存储、用户检索、重排、组装Prompt、模型生成。Java后端在中间能做的事非常多。
我拿一个实际场景举例——“AI设计系统,内置3000余种传统纹样与200余种针法参数”。这个系统的背后就涉及一个知识管理问题:纹样图片、纹样的历史文化描述、颜色参数、针法步骤、适用面料这些数据格式差异很大。如果每次用户提问都把这个数据库里所有内容塞给模型,既超上下文窗口又影响效果。正确做法是把纹样名称、风格、时期、针法、寓意等结构化字段放进数据库,同时把长文本描述做向量化存入向量数据库;用户提问“我想做一个红色绣花茶席”时,先用结构化条件筛选一部分,再做向量相似度召回,最后只把检索到的相关片段拼进Prompt,让模型在这些限定材料内生成设计建议。RAG的另一个好处是每次引入新纹样,不用重新训练模型,只要把新材料做切片和入库,第二天就能被检索到。
再举一个“农业大模型”方向的案例,设备实时监测土壤、气象数据后,智能灌溉施肥。Java这边处理的其实是三件事:高频设备数据先做清洗和削峰;根据监测指标决定是否调用模型服务生成方案;模型给出的灌溉建议只作为辅助输入,最终是否执行需要结合规则引擎判断,并且保留人工审批入口。这个设计能体现出一个后端工程师对AI技术的冷静态度,也就是不盲目信任模型输出,而是设置安全边界。面试时这种思考深度,比堆一堆“深度学习”“神经网络”术语得分高得多。
5.4 AI项目经验的准备角度
聊到AI项目,面试官通常会问“你在这个项目里的具体角色是什么”“效果怎么评测”“失败案例有没有”。针对评测这个问题,如果回答“人工看一眼觉得不错”,基本会被扣分。标准一点的答案是要建立一套评测集:收集一批用户常见问题,标注标准答案,每次调整Prompt、检索逻辑、重排策略后,拿统一评测集跑一遍,统计准确率或人工评分。
还要准备一个“接不住AI生成内容”的兜底故事。我面试时讲过一个例子:用户问题与知识库里的内容完全不沾边时,系统直接回答“暂时无法回答”,而不是让大模型自由发挥。这个阈值判断通常是对检索结果的相似度分数设一个最低值,低于这个分值就直接拦截。它有风险,因为不同领域的最低阈值不一样,需要采样调优。这类细节往往比宏大叙事更能打动面试官。
6. 实战问答复盘:几场面试的现场还原
6.1 一面技术面:排序手写与HashMap底层
一面通常有两道编程题接着就是基础原理追问。编程题我遇到的是从“数组里找出前K个高频元素”和“手写冒泡”二选一。选冒泡时,我把上面那个加布尔标记的改进版本写出来,并解释了最好情况和最坏情况,实际跑过几轮。面试官听完点头,追问了一句“还有没有比冒泡更适合这种场景的”,我又补了“如果K比较小还不如建堆或者用快速选择”。
HashMap这道题几乎人人都要准备。一个比较完整的回答脉络是:put(key,value)先计算key的hashCode,再做扰动计算,然后通过(n-1)&hash定位数组下标;如果该位置为空直接放入,不为空则遍历链表或红黑树找到相同key就覆盖,否则新增节点;链表长度超过8且数组长度超过64时转红黑树,扩容阈值是默认负载因子0.75。再往后可以聊为什么要用尾插法避免JDK7头插带来的死循环问题,以及ConcurrentHashMap分段锁演化成CAS加synchronized的过程。能自然讲到这一层,说明你真的看过源码,而不只是背八股。
6.2 二面项目深挖:从若依微服务版本聊起
不少项目里用若依微服务版本做脚手架,二面时面试官一眼看到就会追问细节。最常见的问题包括“这个框架要自己搭开发环境,有哪些组件是需要提前启动的”“如果网关启动后发现路由不生效怎么排查”。
若依微服务版本依赖一定数量的中间件,通常先是Nacos启动作为注册和配置中心,然后是Redis和MinIO,如果你接入了MQ还要启动RabbitMQ或RocketMQ,最后按gateway、auth、system、文件服务的顺序启动业务模块。开发环境里最典型的问题是Nacos配置没有同步到本地,业务服务报配置缺失,解决办法是检查bootstrap.yml里的namespace、group、dataId以及是否引入了spring-cloud-starter-alibaba-nacos-config。
这里不是单纯考察你会不会用某个开源框架,而是在确认你有没有真正把一个分布式系统从零搭起来过。如果有过排查经验,比如发现网关路由不生效是Nacos上没有注册到服务实例,或者Feign调用超时是ribbon读超时配置太短,这些就是面试里最加分的“现场故事”。
6.3 三面系统设计:消息积压和订单超时关单
这一轮面试官不再盯着具体框架,直接抛系统设计题:“你负责一个订单系统,某天MQ消费者处理速度跟不上,消息积压越来越多,怎么快速恢复、并且以后怎么避免。”这个题的现实感很强。
快速恢复的办法是先别盲目重启应用,要先判断积压原因。如果是消费者SQL慢或者下游接口慢,先定位瓶颈;如果是短时间内流量暴增,可以临时把消息转发到新的临时Topic,启动多份消费者同时消费,消费完再转回原Topic。如果是因为消费者出现死循环或异常导致消息一直重试,那要先让异常消息进入死信队列,避免拖垮后续消息。
以后怎么避免则是在设计方案上做文章:评估单条消息的平均耗时、消费者实例数量、所需吞吐量,提前对Topic做分区扩容;生产端对峰值做削峰填谷;消费者端要做批量消费和线程池隔离。另外还要汇报实时积压量和消费速率滞后情况,不让问题在用户投诉以后才被发现。
另一个常见的方案题是“订单超时未支付自动关闭怎么实现”。优秀回答不是只选一种技术,而是比较多种方案。数据库轮询简单但延迟高;Redis过期key监听并不可靠,因为键过期事件可能丢失;RabbitMQ延迟队列或者RocketMQ定时消息是比较好的方案;如果要求更高,还可以做分层:订单创建时把超时时间写到Redis的ZSet里,由专门任务扫描前N个即将到期的订单,精度高且可控。系统设计题面试官想看到的是你在多种方案里权衡,而不是一门心思推销某个中间件。
三轮面下来我最大的感受是:大厂Java面试越来越像一场“底线验证”,验证候选人到底有没有独立解决过真实问题。Spring Boot和微服务是入场券,AI技术是加分项。如果只背题不思考,遇到场景追问就会露馅。建议大家把重心放在“为什么这么设计”上,基础、框架、项目经验三块都要准备出至少两个能讲完整的故障案例,然后去现场慢慢磨。
