b站(哔哩哔哩)Java 开发一面面经:线程池、AQS、JVM 与取数 Agent
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
会员购部门的一面,面试官开场就指出实习做的是中台、和他们的业务偏离较大;全程无手撕代码。
- 你在滴滴实习后没转正?
- 我们这边在上海,你在北京上学,工作地点你倾向北京还是其他城市?
- 最近还面了哪些公司?
- 目前有拿到 offer 吗?
- 先简单做个自我介绍吧。
- 你简历上写了掌握线程池原理,能讲讲线程池的原理吗?
- 你了解 AQS 吗?它是什么?
- 你了解 JVM 内存结构。Java 不同版本,比如 8、13、17、21,在内存结构和垃圾回收上有什么变化?
- 聚簇索引和非聚簇索引的区别是什么?
- 对一条 SQL 执行 explain 后,你会关注哪些信息?
- 你在滴滴主要是做取数 Agent 吗?
- 你说的分析是指数据分析?
- 生产环境 GC 这块,你还涉及稳定性和执行分级吗?
- 比如分析昨天的 GMV 数据、按人口划分,是这个 Agent 做的吗?
- 你了解哪些 Agent 相关技术?
- 什么是 ReAct?你接触过 LangChain、LangGraph 这些框架,或者阿里相关的 AI 框架吗?
- 你在滴滴具体负责什么?
- 讲讲 GC 治理的过程吧。
- CSV 读取和浏览功能比较耗内存,还有加载两次的问题,对吗?
- 数据资产批量查询,我理解是调用下游接口,用线程池批量执行,再合并结果,对吗?
- 那些接口都是不同组提供的?
- 校园招聘智能助手是你自己做的项目吗?里面也用了 AI 辅助开发?讲讲这个项目。
- 这个项目是你自己练手的,还是课题?
- 里面的多 Agent 内核是什么意思?
- 这个项目是中心化部署吗?
- 也就是说,它不是客户端,而是服务端?用户向服务器提交求职请求,服务器收集信息后再返回?
- 渠道抽象、三重分类、系统命令、关键词打分、拦截消息这些描述,是 AI 总结出来的?
- 你讲讲对 Spring 事件总线的理解?
- 事件总线和发布订阅模式、消息队列有什么区别?
- 电商优惠券平台也是个人练手项目?
- 你在滴滴的实习偏中台和基础,业务性不够强,你怎么看?后续做研发,你对哪方面更感兴趣?
- 现在 AI 发展越来越快,对程序员这一行影响很大,你怎么看程序员行业未来的发展?
- 现在纯写代码的部分,AI 可能十分钟就能完成,你怎么看?
《参考解析》
线程池原理要讲到「为什么是这个顺序」。ThreadPoolExecutor 的七参数是核心线程数、最大线程数、空闲存活时间与单位、任务队列、线程工厂、拒绝策略。执行顺序必须准:核心线程未满就新建核心线程;核心满了投队列;队列满了才扩到最大线程数;再满走拒绝策略。面试官真正想听的是这个设计带来的后果:用无界队列(默认 LinkedBlockingQueue)时最大线程数永远不生效,任务无限堆积直到 OOM,所以生产必须给队列设容量;CallerRunsPolicy 让提交线程自己执行,形成天然反压,适合不能丢又要限速的场景;AbortPolicy 会抛异常,得在业务侧接住。还要能延伸线程数怎么定(CPU 密集型约核数加一,IO 密集型按等待时间与计算时间之比估算,最终靠压测),以及监控要看活跃线程数、队列长度、拒绝次数和任务耗时,多个业务要用不同的池隔离,父子任务共用池并互相等待会死锁。
AQS 是理解所有同步器的钥匙。AQS 用一个 volatile int state 表示同步状态,加一个 FIFO 的双向等待队列(CLH 变体的实现),用 CAS 修改 state 与队列指针,通过模板方法把「如何获取/释放」交给子类实现——ReentrantLock 用 state 记录重入次数,Semaphore 用它表示许可数,CountDownLatch 用它表示剩余计数,ReentrantReadWriteLock 把 state 高低位拆成读锁与写锁计数。独占模式走 acquire/release,共享模式走 acquireShared/releaseShared;获取失败入队并 LockSupport.park 阻塞,前驱唤醒后重试。答得出「state + 队列 + 模板方法 + 独占/共享」这四点,再补一句 ReentrantLock 的公平与非公平差别(非公平会先直接 CAS 抢一次,吞吐更高但可能饥饿),就足够了。
JDK 8/13/17/21 在内存结构与 GC 上的变化。内存结构本身变化不大,主线是方法区的实现:JDK 8 把永久代换成了元空间,使用本地内存,同时默认 GC 还是 Parallel;JDK 9 起 G1 成为服务端默认,模块化也让类元数据更紧凑。GC 的演进才是重点:JDK 11 引入 ZGC 与 Epsilon(实验特性),JDK 12 加入 Shenandoah,JDK 14 移除了 CMS,JDK 15 让 ZGC 与 Shenandoah 转正并默认禁用偏向锁(JDK 18 彻底移除相关实现),JDK 17 是当前主流的 LTS,G1 可调参数完善,密封类等语言特性转正;JDK 21 是新的 LTS,分代 ZGC 与虚拟线程转正,记录模式与模式匹配进入正式版。回答时可以用一句话把逻辑串起来:分代收集的假设仍然有效(绝大多数对象朝生夕死),所以低延迟收集器也在往分代走;选型的判据是堆大小、停顿目标与吞吐要求,而不是「版本越新越好」。
索引与 explain 的关注点。聚簇索引决定数据的物理存放顺序,InnoDB 的主键索引就是聚簇索引,叶子节点直接存整行数据,因此一张表只有一个聚簇索引;非聚簇索引(二级索引)叶子节点存的是索引列加主键值,回表就是拿主键再去聚簇索引取整行——这也是「覆盖索引能省一次回表」的原因。用自增主键而不是随机 UUID 做聚簇索引键,能避免页分裂与随机写放大。explain 要按顺序看:type(const/eq_ref/ref/range/index/all,出现 ALL 基本意味着全表扫描)、key 与实际使用的索引(可能与 possible_keys 不同)、rows 与 filtered 估计扫描与过滤比例、Extra 里的关键信号(Using filesort、Using temporary 往往意味着排序或分组没有走索引,Using index 是覆盖索引的好信号)。再补一句「explain 的 rows 是估算值,真实代价要靠慢查询日志与 explain analyze」,能显出你踩过坑。
取数 Agent、GC 治理与 CSV 双加载。这几问的答法要落到具体机制上。取数 Agent 的形态通常是「把自然语言需求翻译成查询或编排多个下游接口」,核心不在提示词而在可靠性:意图识别与澄清、字段与口径映射(指标定义必须来自统一元数据,不能让模型自由发挥)、查询生成后的校验与限流、以及结果的可解释性(把生成的 SQL 或调用链展示出来让人复核)。GC 治理的过程要有闭环:先用 jstat/GC 日志确认现象(Full GC 频率、单次停顿、老年代增长曲线),区分是内存泄漏(每次回收后底水位是否在涨)还是分配速率过高(大对象、批量加载、缓存无上限),再用 jmap/堆转储加 MAT 找到支配树上的大对象;对策分三层——减少不必要的对象与拷贝(流式解析代替一次性加载、复用缓冲)、给缓存设容量与过期策略、调整堆与收集器参数;完成后用同样的压测场景复测,并把监控告警留在线上。CSV 的「加载两次」这类问题,典型原因是同一份数据先被整体读进内存又建了一份索引/映射,或者是框架的懒加载与预加载叠加,治理方向是流式读取、按需索引、软引用缓存,而不是简单加内存——加内存只是把 OOM 推迟,掩盖的是放大倍数。