Skip to content

AI+Java中级开发面试题库 ​

生成时间:2026-05-20 23:07 | 共10题


【面试题】1. RAG系统中检索召回率低,常见的优化手段有哪些?请从索引、检索、后处理三个层面说明。 ​

【标准答案】 索引层面:①chunk大小要和query语义粒度匹配,一般256-512 token,太长噪声多、太短丢上下文;②加元数据过滤(时间、部门、标签),缩小检索范围;③对长文档做摘要索引+原文索引的双路召回。 检索层面:①混合检索,BM25抓关键词+向量语义检索抓语义,加权融合比单路效果好很多;②query改写,用LLM把用户模糊问题拆解成多个子查询并行检索;③embedding模型选中文场景好一点的,比如bge-large-zh,别用通用英文模型硬套。 后处理层面:①用cross-encoder重排序,把初筛top-20精排到top-5再送LLM;②去重去噪,相似度>0.95的chunk只保留一个;③加rerank后的context压缩,只保留和query相关的句子,减少token浪费。 实际项目中,混合检索+cross-encoder重排这两步收益最大,通常能把召回准确率从60%拉到85%以上。

【面试题】2. Java中ConcurrentHashMap在JDK 7和JDK 8中的实现有什么核心区别?为什么JDK 8要改成CAS+synchronized? ​

【标准答案】 JDK 7用的是分段锁(Segment),默认16个Segment,每个Segment本质上是一个小HashMap加一把ReentrantLock。并发度受Segment数限制,锁粒度是Segment级别。 JDK 8取消了Segment,改为对每个桶的头节点加锁,锁粒度从Segment级别细化到桶级别,并发度大幅提升。插入时先用CAS尝试写入空桶,失败再升级为synchronized锁住头节点。 为什么改:①Segment方案并发度固定,扩容后Segment数不变,高并发场景锁竞争严重;②synchronized在JDK 6之后做了大量优化(偏向锁→轻量级锁→重量级锁升级),大多数场景下性能不输ReentrantLock,还省了AQS的内存开销;③细粒度锁让冲突概率大大降低,CAS一次成功的概率很高。 另外JDK 8还把链表转红黑树的阈值设为8,长链表查询从O(n)变成O(logn),极端hash冲突场景性能提升明显。

【面试题】3. 你在项目中用Spring AI开发Agent时,如何实现工具调用(Tool/Function Calling)的?工具注册和执行流程是怎样的? ​

【标准答案】 Spring AI通过FunctionCallback接口实现工具调用。核心流程四步走: 第一步,定义工具。实现Function<I,O>接口,或用@Bean+@Description注解声明函数,Spring AI自动把它注册为可调用工具。 第二步,注册到ChatClient。创建ChatClient时指定tools,框架会把工具的name、description、参数schema序列化到请求中发给大模型。 第三步,模型决策。模型根据用户意图判断是否需要调用工具,如果需要,返回一个tool_call响应,包含工具名和参数。 第四步,框架自动执行。Spring AI拦截tool_call,根据name路由到对应的FunctionCallback执行,把结果拼回对话再送模型,模型给出最终回答。 关键注意点:①工具描述要精准,这是模型选对工具的唯一依据;②参数用record或POJO定义,框架自动生成JSON Schema;③设置maxToolCalls防止循环调用;④工具执行失败要有容错,返回错误信息让模型自行判断是否重试。

【面试题】4. MySQL中什么是索引下推(Index Condition Pushdown,ICP)?它如何提升查询性能?举个具体例子。 ​

【标准答案】 ICP是MySQL 5.6引入的优化,核心思想:把WHERE中能在索引上评估的条件"下推"到存储引擎层执行,减少回表次数。 例子:表有联合索引(name, age),查询SELECT * FROM user WHERE name LIKE '张%' AND age > 25。 没有ICP时:存储引擎通过name范围扫描找到所有name以"张"开头的记录,每条都回表取整行,再在Server层逐行判断age>25。 有ICP时:存储引擎在索引中就能判断age>25这个条件,不满足的记录直接跳过,只对满足条件的记录回表。假如name以"张"开头有100条,其中age>25只有10条,回表次数从100次降到10次。 适用条件:①联合索引;②WHERE条件包含索引列但无法全部用于索引查找(比如LIKE前缀、范围查询后的列);③InnoDB引擎的二级索引。 验证:EXPLAIN的Extra列出现"Using index condition"就说明用了ICP。

【面试题】5. 大模型推理时出现"幻觉",在工程上有哪些有效的缓解手段? ​

【标准答案】 工程上缓解幻觉主要四个方向: 第一,RAG兜底。把事实性知识放知识库,模型回答时先检索相关文档作为context,让生成有据可依。这是目前工业界最实用的方案。 第二,提示词约束。在system prompt中明确要求"只根据提供的上下文回答,无法回答时请说明",降低模型自由编造的倾向。 第三,后处理校验。用另一个LLM做事实性审查(self-consistency或verifier模型),或者对接知识图谱做实体关系校验,对高置信度幻觉做拦截。 第四,参数调控。调低temperature(0-0.3),减少采样随机性;增加repetition_penalty抑制重复编造;用约束解码(constrained decoding)限制输出格式和取值范围。 最重要的认知:幻觉是生成式模型的内在特性,无法100%消除。工程上要做的是"可控降级"——对于事实性场景,RAG+低temperature+后校验三管齐下,把幻觉率压到业务可接受范围。

【面试题】6. 请解释Java中volatile关键字的作用,它为什么不能保证原子性?举例说明。 ​

【标准答案】 volatile两大作用:①保证可见性,写操作强制刷新到主内存,读操作强制从主内存读取,线程间立即可见;②禁止指令重排序,通过插入内存屏障实现。 为什么不能保证原子性:volatile只保证读和写各自的可见性,但不保证"读-改-写"这个复合操作的原子性。经典例子:两个线程同时执行volatile int count; count++,count++字节码是三步操作(读取→+1→写回),线程A读到count=0,线程B也读到count=0,各自加1写回,结果是1而不是2,丢失了一次更新。 解决原子性的正确做法:①用AtomicInteger,底层CAS保证原子性;②加synchronized或ReentrantLock;③用LongAdder(高并发场景比AtomicInteger性能更好,分段累加)。 volatile适用场景:状态标志位(boolean running)、单次写入的配置值、DCL双重检查锁中的单例引用。

【面试题】7. 向量数据库的HNSW索引原理是什么?它为什么查询速度快?有什么 Trade-off? ​

【标准答案】 HNSW(Hierarchical Navigable Small World)是一种基于图的近似最近邻索引算法。 原理:构建多层图结构,上层稀疏下层稠密。查询时从顶层入口点出发,贪心搜索找到离目标最近的节点,然后跳到下一层从该节点继续搜索,逐层细化,直到最底层找到最近邻。类似跳表的思路,上层快速定位区域,下层精确查找。 为什么快:①图结构保证每个节点和近邻直接相连,搜索路径短;②分层结构让跳转步长指数级缩小,对数级别复杂度;③查询只访问图的一小部分节点,不需要全量扫描。 Trade-off:①精度换速度,结果是近似最近邻,召回率<100%,可通过efSearch参数调节;②内存占用大,每个向量要维护多层邻居关系,通常比原始数据大2-3倍;③构建慢,插入时要做邻居选择和层级分配,不适合频繁更新的场景。 工程选型:Milvus、Qdrant、Weaviate都支持HNSW,一般efConstruction=200、M=16是常用的起点参数,召回率可达95%以上。

【面试题】8. Redis分布式锁用SET NX EX实现时,有哪些坑?Redisson是怎么解决的? ​

【标准答案】 SET NX EX的三大坑: 坑一:锁过期释放但业务没执行完。线程A拿到锁,执行超时,锁自动过期,线程B又拿到锁,A执行完释放了B的锁。 Redisson解决:看门狗机制(Watchdog)。默认锁30秒,后台线程每10秒续期一次,业务没完成就自动续命,业务完成主动释放。 坑二:误删别人的锁。A的锁过期后B加锁成功,A执行完直接DEL把B的锁删了。 Redisson解决:锁value用UUID+线程ID唯一标识,释放时Lua脚本先比较value再删除,保证只删自己的锁。 坑三:主从切换丢锁。主节点加锁成功还没同步到从节点就宕机,从节点升为主后没有锁信息,其他线程又能加锁。 Redisson解决:RedLock算法,向多个独立Redis实例同时加锁,过半成功才算获取锁。但RedLock有争议,Martin Kleppmann写过文章质疑其安全性。 实际建议:大部分场景用Redisson单节点锁+看门狗就够了。对一致性要求极高的场景考虑Zookeeper或etcd的分布式锁。

【面试题】9. 设计一个AI客服系统的架构,要求能支持多轮对话、知识库问答、人工转接,你会怎么设计? ​

【标准答案】 整体分四层: 接入层:WebSocket/HTTP网关,负责会话管理、消息收发、限流。每个用户一个sessionId,维护对话历史。 对话引擎层:核心是LangChain或Spring AI编排的Agent。①意图识别:先判断用户意图是闲聊、知识库问答还是业务办理;②知识库问答走RAG链路:query改写→混合检索→重排序→LLM生成;③多轮对话用ConversationBufferWindow维护最近K轮历史,超出用摘要压缩;④人工转接:当置信度低于阈值或用户主动要求时触发。 知识库层:文档入库管道(解析→分块→向量化→写入),向量数据库用Milvus/Qdrant,同时维护ES做关键词检索。知识库定期增量更新,更新时做版本管理避免脏数据。 运维层:①对话日志全量落ES,用于审计和效果分析;②埋点统计意图识别准确率、RAG召回率、人工转接率;③LLM调用做token计费和降级(主模型不可用时切备用模型)。 关键设计点:多轮对话的上下文管理用滑动窗口+摘要,别把全量历史塞进去浪费token;人工转接要把之前对话摘要一起给人工坐席,避免客户重复说问题。

【面试题】10. JVM中Full GC频繁触发,如何排查和优化?说出你的排查思路和常见原因。 ​

【标准答案】 排查思路四步走: 第一步,看GC日志确认Full GC频率和耗时。-Xlog:gc*打印详细日志,关注Full GC间隔和耗时,如果几分钟一次甚至几十秒一次就严重了。 第二步,用jstat -gcutil观察各区域使用率,看是老年代一直满、Metaspace满、还是System.gc()被调用。 第三步,jmap -histo:live看存活对象分布,找出占用最大的类;如果需要,jmap -dump:format=b,file=heap.hprof导出快照用MAT分析。 第四步,定位根因,对症下药。 常见原因及优化: ①内存泄漏:对象被集合或缓存持有无法回收。用MAT找Dominator Tree中最大对象,检查是否有static集合一直put不remove。 ②大对象直接进老年代:大数组或大字符串超过-XX:PretenureSizeThreshold直接晋升。优化:减少单次分配大小,或调大阈值。 ③Metaspace溢出:动态代理/CGLIB大量生成类。加-XX:MaxMetaspaceSize,检查是否有类泄漏。 ④显式System.gc():NIO的DirectByteBuffer回收时会调用。加-XX:+DisableExplicitGC禁止。 ⑤老年代空间不足:年轻代太小导致对象过早晋升。调大新生代比例,让对象在Minor GC时就被回收。 预防措施:上线前压测观察GC,设置-XX:+HeapDumpOnOutOfMemoryError自动dump,接入Prometheus+Grafana监控GC指标。