面灵AI→

恒生电子AI应用开发AI面试:JVM、InnoDB加锁与RAG切分

轮次
AI面试
时间
2026-09
来源
牛客网

《面试题目》

一、JVM 类加载与双亲委派模型

  1. JVM 的类加载完整过程以及双亲委派模型的核心逻辑是什么?
  2. 双亲委派的层级委托逻辑下,自定义一个与核心类库完全同名的类(如 java.lang.String),会被 JVM 加载吗?
  3. JVM 通过什么具体机制保证核心类库不被自定义类加载器篡改?

二、MySQL InnoDB 事务与锁机制

  1. InnoDB 执行 update 语句,条件分别是主键、唯一键、普通非索引字段时,加锁逻辑有什么区别?
  2. 普通非索引字段作为 update 条件会扫描全表加聚簇索引记录锁,在可重复读隔离级别下,会额外锁住哪些不存在的间隙范围?
  3. InnoDB 全表扫描更新时,为什么不会做锁优化、无法只精准锁定符合条件的记录?

三、大模型 Function Calling 核心机制

  1. Function Calling 如何实现大模型调用外部 API?模型本身无法执行函数,在整个机制中承担的核心作用是什么?
  2. 用户需求需要连续调用多个不同工具时,模型在多轮工具调用过程中具体承担什么核心工作?
  3. 如何解决大模型生成的函数调用参数不符合后端 API 规范、参数错误、格式非法的问题?

四、RAG 系统设计与检索优化

  1. 标准 RAG 从用户输入到最终答案输出的完整架构流程是什么?每个核心环节的作用是什么?
  2. 针对跨章节、逻辑强关联的超长文档,如何设计分块策略避免语义割裂?
  3. 语义感知加层级多粒度分块,对比传统固定长度分块,会给检索召回环节带来哪些全新挑战?
  4. 固定长度切分在投研、财报这类专业文档场景下,会出现哪些典型失效问题、割裂业务逻辑?
  5. 语义感知层级多粒度分块的具体方案是什么?如何识别专业文档的逻辑边界?不同粒度的分块如何做关联绑定?
  6. 向量召回容易召回语义相似但业务口径不匹配的片段,如何量化验证原有召回方案的缺陷?用到哪些评测指标和测试样本?
  7. 什么是口径匹配率?该指标的具体定义、计算方式、解决的核心问题是什么?

五、多智能体(Multi-Agent)系统架构

  1. 单体 Agent 存在能力瓶颈、无法处理复杂任务时,如何设计多智能体解决方案?多 Agent 的通信、协作、调度机制怎么设计?
  2. 多智能体系统中,两个执行 Agent 对同一业务事实输出矛盾结果,如何设计合理的仲裁机制?
  3. 多个 Agent 依赖的原始参考资料本身存在信息冲突时,系统依据什么优先级原则判定采信规则?
  4. 从单体 Agent 升级为职责分离多 Agent 加黑板调度方案,需要放弃哪些优化路线?最终选型的核心原因是什么?

六、AI 工程落地与 Spring AI 框架

  1. 主流 AI 应用开发框架的核心价值是什么?以 Spring AI 为例,其核心设计巧思、解决的行业痛点、落地适用场景有哪些?
  2. 没有 Spring AI 时,Java 生态对接大模型、向量库的核心适配痛点是什么?
  3. Spring AI 针对大模型工具调用,核心抽象层的设计原理是什么?如何屏蔽各家大模型的 API 差异?
  4. Spring AI 在复杂 RAG、多 Agent 场景下存在哪些原生短板?实际落地会踩哪些核心坑?

《参考解析》

类加载过程与双亲委派:类加载分加载、验证、准备、解析、初始化五个阶段(之后还有使用与卸载)。加载阶段由类加载器读取字节码生成 Class 对象;验证检查字节码合法性与安全性;准备为静态变量分配内存并赋类型零值;解析把符号引用替换为直接引用;初始化才真正执行静态代码块与静态变量赋值。双亲委派是「收到加载请求先交给父加载器,父加载器无法完成才自己加载」:Bootstrap → Extension/Platform → Application → 自定义。它的价值是保证同一个类在各类加载器视野里只有一份、避免核心类库被替换,也避免了类的重复加载与类型冲突。

自定义 java.lang.String 会被加载吗:写一个同名的 java.lang.String 编译能过(编译期是允许的),但运行时不会生效。JVM 对以 java. 开头的类做了包级保护:双亲委派会先把请求交给 Bootstrap,Bootstrap 在 rt.jar/jrt 里已经找到了真正的 java.lang.String,直接返回,自定义类加载器根本没有机会加载;即使绕过双亲委派(重写 loadClass 不委派)强行加载,defineClass 也会因为「禁止加载 java.* 包下的类」抛 SecurityException。

靠什么机制保证核心类库不被篡改:三重防线。一是双亲委派模型本身,让核心类永远由最顶层的 Bootstrap 加载,应用类加载器拿不到定义权。二是 ClassLoader.preDefineClass 里的包名与保护域检查,显式拒绝以 java. 开头的类名(JVM 层还有相应的 native 校验),这是真正的硬闸。三是保护域(ProtectionDomain)与签名校验——核心 jar 带证书签名,加载时会校验,篡改字节码会导致签名不匹配。要讲清楚的一点是:双亲委派是「约定 + 默认实现」,可以被重写绕过,真正兜底的是 JVM 层对 java.* 的定义禁令。

InnoDB 按不同条件 update 的加锁差异:条件命中主键(或唯一索引且值存在)时走唯一索引等值查询,只需在聚簇索引上给这一行加记录锁(X 锁),不加间隙锁,锁范围最小。条件命中唯一索引但值不存在时,会退化成间隙锁(Gap 锁)锁住该值所在的间隙防止并发插入。条件走普通二级索引时,会先在二级索引上加记录锁与间隙锁,再回表给聚簇索引上加记录锁——注意这里有两套锁,隔离级别为 RR 时二级索引上还会用 Next-Key 锁(记录锁 + 前面间隙的间隙锁)来解决幻读。条件是完全没有索引的字段时,只能全表扫描聚簇索引,扫描路径上遇到的所有记录都会被加锁,且 RR 下每条记录还会连带其前面的间隙,效果等同于锁全表——这正是「线上用非索引字段做 update 容易把库锁死」的原因。

全表扫描为什么不能只锁符合条件的行:因为加锁发生在扫描阶段而不是过滤阶段。InnoDB 是边扫描边加锁的:读到的每一行都先加锁,再去判断是否符合 WHERE 条件,不符合的虽然会被 MySQL 层提前释放(semi-consistent read / 条件下推的优化在 RC 下生效),但在 RR 隔离级别下这些锁要保留到事务提交,所以扫描路径上的所有行都会被锁住。根本原因是「不做锁优化」才安全——如果跳过某些行不加锁,那些行在事务提交前被并发修改,就可能出现同一事务内两次读到不同结果(不可重复读)或幻读,破坏 RR 的语义保证。

Function Calling 的实现原理:模型本身不执行任何函数,它承担的职责是「把自然语言意图翻译成结构化的调用请求」——在工具定义(名称、描述、参数的 JSON Schema)被注入上下文后,模型在推理时输出一段符合该 Schema 的结构化 JSON(函数名 + 参数),运行时(Agent 框架)解析这段输出、校验参数、真正发起 HTTP/RPC 调用、拿到结果后再以工具消息的格式回填给模型,模型据此继续推理或生成最终回答。所以分工是:模型负责「选哪个工具、填什么参数」这种语义决策,程序负责执行与安全边界。模型能做得好的前提是工具描述足够清晰——描述本质上就是给模型的 prompt,工具命名模糊、描述含糊是选错工具的首要原因。

连续调用多个工具时模型做什么:在多轮工具调用循环里,模型承担的是「规划与状态推进」:每一步根据已有的工具返回结果判断目标是否达成、下一步该调哪个工具、参数如何基于前面的结果推导。所以它同时在做三件事——任务分解(把复合需求拆成有依赖顺序的子步骤)、参数传递(后一个工具的入参来自前一个工具的输出,需要从结果里正确抽取字段)、以及终止判断(决定继续调用还是给出最终答案)。工程上要注意的是这个循环必须有最大轮数上限与重复调用检测,否则模型可能在两个工具之间来回震荡。

参数不符合 API 规范怎么办:分四层治理。第一层在 Schema 上做约束:把参数类型、枚举取值、必填项、数值范围、格式(如日期用 format: date)都写进 JSON Schema,让模型有明确的形状可依;能枚举就不要用自由文本。第二层在服务端做严格校验:拿 Schema 做一次真实校验(不是靠模型自述),不合法就带着具体的错误信息(哪个字段、期望什么、收到什么)打回给模型重生成,同时限制重试次数。第三层做参数的规范化与容错:类型可安全转换的(字符串 “5” 转数字 5)、大小写与空白的差异、常见别名的映射,在网关层直接纠正而不必占用一轮模型调用;但涉及金额、权限、目标对象这类关键参数一律不做猜测式纠正。第四层是让模型少犯错:给 few-shot 示例展示正确调用、把容易混淆的参数在描述里写清区别、必要时拆成多个职责单一的工具而不是一个参数众多的巨无霸工具。生产上还要给每个写操作带上幂等标识,防止重试造成重复执行。

标准 RAG 的完整流程:分索引段与查询段。索引段:文档解析(PDF/HTML/Word 抽正文,处理表格与图片)、清洗去噪、分块、embedding、写入向量库并保留元数据(来源、页码、章节路径、时间)。查询段:查询理解与改写(纠错、消歧、多角度扩展)、检索(向量召回 + 关键词召回并行)、融合(RRF 等)、重排序(Cross-Encoder 精排取 Top-K)、上下文构建(去重、按相关性排序、必要时压缩或摘要)、生成(带引用约束的 prompt)、以及后置的事实校验与引用回填。每个环节的价值不同:分块决定召回的上限,检索决定召回率,重排决定精确率,上下文构建决定模型能不能用上,生成侧的约束决定幻觉率。

跨章节强关联文档怎么分块:固定长度切分在这种文档上必然失效,所以要按文档自身的结构切。做法是分层:先按标题层级(章、节、小节)切出逻辑单元,再在每个单元内部按段落切;跨章节的强关联(比如「见第三章的假设」「以上口径适用于全部报表」)要靠元数据与引用关系保留——给每个块打上章节路径、前后块 id、以及显式引用链接,检索时命中一块就把它的兄弟块与引用块一起带出来。更完整的方案是父子结构:子块(段落级)用于检索,父块(小节或章节级)用于生成,命中子块后返回父块内容。另外对表格要单独处理(整表保留并转成结构化文本)、对公式与术语表要额外建一张术语到定义的映射,避免「语义相似但口径不同」的召回。

语义感知多粒度分块给检索带来的新挑战:主要四条。一是块长度分布极不均匀,长块会稀释向量语义、短块又信息不足,同一个 query 对不同长度块的相似度不可比——解法是召回时对不同粒度分别检索再融合,或给相似度做长度归一。二是块数量与层级变多导致检索目标变模糊,需要决定在哪一层检索、命中后如何向上向下扩展,处理不好会召回大量冗余的邻近块。三是父子/兄弟关系的维护成本:文档更新时要同步更新层级关系,否则出现悬空的父块引用。四是评测变复杂,传统的 Recall@k 不再能直接衡量效果,要按粒度分别评估并额外看「召回块是否覆盖了回答所需的全部证据」。指标上除了 Recall@k、MRR、nDCG,还要补一个「证据覆盖率」和口径一致性的指标。

固定长度切分在投研财报场景的失效:典型问题有四类。一是切断语义单元——把一张财务表格、一段风险披露、一条会计政策从中间截断,检索到的片段无法独立理解。二是割裂口径与前提——财报里的数字往往依赖「本期/上期」「合并/母公司」「调整前/调整后」这些限定,被切开后模型可能拿错口径的数。三是打断因果与条件链——「若某项达到 X 则计入 Y」这类规则被拆散,模型只看到后半句就会误用。四是丢失结构位置——同一句话在「管理层讨论」和「审计意见」里的含义完全不同,固定长度切分丢掉了章节归属。这四类问题的共同后果是召回片段看起来语义相关,但业务口径不匹配,最终答案数字对不上——这也是必须引入口径匹配率这类指标的原因。

口径匹配率:它是为「向量相似度衡量不了业务口径」这个缺口设计的补充指标。定义上,口径匹配率 = 召回片段中「与问题所需口径一致」的比例,除以召回总数——分子分母都要由人工标注或规则判定「口径标签」是否一致,口径标签包括时间区间(本期/去年同期)、报表范围(合并/母公司)、币种与单位、调整状态、统计维度(区域/产品线)等。计算方式一般是建一个有标准口径标注的评测集,对每条 query 跑一遍召回,逐条比对召回片段的元数据与 query 期望口径,统计匹配比例。它解决的核心问题是:把「语义相似但口径错」和「语义与口径都对」区分开——前者在纯向量指标里得分很高,但会直接导致答案数值错误,是专业文档场景最危险的失败模式。实务上它要和 Recall@k 一起看:Recall 高而口径匹配率低,说明检索该加结构化过滤(时间、报表范围等元数据过滤)而不是换 embedding 模型。

多智能体的通信、协作与调度:结构上分三类角色:规划者(Orchestrator/Planner)负责拆解目标与编排流程,执行者(Worker)负责各自领域的具体动作,评审者(Critic/Verifier)负责校验结果。通信有三种模式——共享黑板(所有 Agent 读写同一块结构化状态,适合需要全局视野的任务)、点对点消息(Agent 之间直接请求,适合依赖关系明确的流水线)、事件总线(发布订阅,适合松耦合与并行);生产上通常是黑板上放共享事实、消息队列承载任务投递。调度要解决三件事:依赖解析(把任务建成 DAG,无依赖的并行、有依赖的按拓扑序)、资源分配(并发上限、超时与重试、把长任务异步化)、以及失败处理(某个 Worker 失败是重试、降级还是整体回滚)。工程上必须有的护栏是最大轮数/最大步数、全局超时、以及每个 Agent 的成本预算。

两个 Agent 输出矛盾结果怎么仲裁:按成本从低到高排一个仲裁阶梯。第一层是规则裁决:如果矛盾可以归因于数据版本不同(一个用了旧快照),就统一到同一数据快照重算,这类矛盾根本不该由模型拍板。第二层是证据比对:把两个结论各自的支撑证据拉出来,谁引用的是权威源(原始数据库、有版本号的文件)谁胜出,这比让模型「再想想」可靠得多。第三层是加权投票:给每个 Agent 按历史准确率、任务相关度、以及当前置信度赋权,权重可配。第四层才引入独立仲裁者(一个专门做裁判的 Agent 或人工),并且要求它必须给出理由和引用,不能只给结论。无论用哪一层,都要把矛盾本身落库做为评测样本——反复出现的同类矛盾说明是流程或缺省规则的问题,不是模型的问题。

原始资料本身冲突时的采信优先级:要有一套写死的、可解释的优先级规则,而不是每次让模型自己权衡。常见顺序是:权威性(官方发布/合同原文 > 内部解读 > 第三方转述)→ 时效性(有生效时间戳的取最新,历史版本仅在追溯场景使用)→ 特定性(专门针对该业务场景的口径优先于通用口径)→ 可验证性(能追溯到原始出处的优于无法溯源的)。同时系统要做两件事:一是给每个文档片段带上这些元数据(来源等级、生效时间、适用范围),让排序可自动化;二是当冲突无法按规则消解时,不要静默选一个,而是把两种说法都呈现给用户并标明出处与差异——在金融、合规这类场景里,暴露不确定性比给一个错误的确切答案更负责。

单体 Agent 升级到多 Agent 加黑板要放弃什么:主要是三样东西。一是「全局最优的单次推理」——多 Agent 拆开后每个 Agent 的信息都是局部的,协调过程本身有信息损耗,可能出现各自都对但合起来不对的情况;二是低延迟与低成本——多一次 Agent 就多一次模型调用和上下文传递,总延迟与 token 成本都会上升;三是可预测性——单体 Agent 的执行路径容易复现和调试,多 Agent 的调度依赖运行时状态,同一个输入两次跑可能走不同的分工路径。选它的核心原因是可维护性与可扩展性:职责分离后每个 Agent 的工具集与权限都能收窄(安全性提升)、可以独立评测与迭代(改一个不牵动全局)、并且能突破单次上下文窗口的限制。判断标准是「任务的复杂度是否真的超过了一个 Agent 加一套好工具的承载能力」——没有这个前提,多 Agent 只是把复杂度从代码搬到了调度上。

AI 应用框架的核心价值与 Spring AI:框架的核心价值是「把与具体模型厂商无关的通用流程收敛成抽象」——模型调用、prompt 模板、对话记忆、向量库接入、工具调用、结构化输出、可观测性。Spring AI 的设计巧思在于它把 Java 生态既有的抽象习惯(类似 JdbcTemplate 的 Template 模式、ChatClient 的流式 API、自动装配与配置属性)搬到 AI 场景,让 Java 团队不必切换到 Python 也能接大模型;它解决的痛点是 Java 侧过去要针对每家厂商各写一套 HTTP 客户端与参数拼装、还要自己维护向量库 SDK,现在换模型只改配置。适合的场景是企业内部已有 Spring 技术栈、需要与既有鉴权与事务体系深度集成的系统;反过来,如果业务需要最快跟进模型侧最新能力(新的多模态、新的 reasoning 参数),Java 生态的跟进速度通常慢于 Python,这一点要提前认。

没有框架时 Java 生态的适配痛点:主要是四类重复劳动。一是每家厂商的协议都不同:OpenAI 兼容格式与各家自有格式(鉴权方式、消息结构、流式返回的 SSE 分帧、错误码)都要各写一套,换模型等于重写。二是流式返回的处理:SSE 分帧解析、粘包处理、背压、以及流式与结构化输出的冲突,手写容易出 bug。三是向量库的适配:不同向量库的 SDK、集合与索引管理、过滤条件语法、批量写入的限流都不一样。四是公共能力的缺失:重试与降级、token 计数与成本统计、prompt 模板管理、调用链路的可观测性——这些每个项目都要重造一遍。框架的本质价值就是把这些横切关注点收敛到一层,让业务代码只表达「要什么」。

Spring AI 屏蔽厂商差异的抽象设计:核心是两层抽象。第一层是模型调用抽象(ChatModel 接口),把「对话请求」定义成厂商无关的对象(消息列表、模型参数、工具定义),各厂商提供自己的实现(OpenAI、Anthropic、DashScope 各一个),调用方只依赖接口;进阶能力(流式、多模态、结构化输出)也统一在接口上,实现类内部做协议转换。第二层是工具调用的抽象,把方法用注解标记成工具(类似 @Tool),框架启动时反射扫描出方法签名并自动生成 JSON Schema 定义,调用时再把模型返回的参数反序列化成方法入参并执行——开发者写的是普通 Java 方法,框架负责 Schema 生成、参数绑定与结果序列化。屏蔽差异的关键在于「归一化到最小公共能力集 + 各实现补齐差异」这个模式:公共能力走统一接口,厂商独有能力通过实现类特有的方法暴露,避免抽象层出现「为了兼容所有人而谁都用不好」的宽接口。

Spring AI 在复杂 RAG 与多 Agent 场景的短板:RAG 侧,它的向量库抽象和文档读取器覆盖的是「标准流水线」,一旦要做语义感知分块、父子块索引、混合检索加自定义 rerank、按元数据做复杂过滤,就需要大量绕过框架直接操作底层;上下文构建与压缩策略也几乎没有可插拔的空间。Agent 侧,它的工具调用是「单轮的一次调用」范式,缺少原生的多轮规划循环、状态机编排、检查点与人工介入(interrupt),做复杂 Agent 基本要自己实现调度层。工程上常见的坑:流式输出与工具调用同时开启时的顺序处理容易出问题;版本迭代快、API 有破坏性变更,升级要仔细看 release notes;模型特有的参数(思考等级、缓存控制)在统一抽象里表达不了,只能下探到实现类,等于部分放弃抽象收益。结论是:简单问答/标准 RAG 用框架省事,复杂的 Agent 编排更适合自己控制主循环、只把框架用在模型接入这一层。