面灵AI→

b站(哔哩哔哩)Java 开发一面面经:线程池、AQS、JVM 与取数 Agent

轮次
一面
时间
2026-10
来源
牛客网

《面试题目》

会员购部门的一面,面试官开场就指出实习做的是中台、和他们的业务偏离较大;全程无手撕代码。

  1. 你在滴滴实习后没转正?
  2. 我们这边在上海,你在北京上学,工作地点你倾向北京还是其他城市?
  3. 最近还面了哪些公司?
  4. 目前有拿到 offer 吗?
  5. 先简单做个自我介绍吧。
  6. 你简历上写了掌握线程池原理,能讲讲线程池的原理吗?
  7. 你了解 AQS 吗?它是什么?
  8. 你了解 JVM 内存结构。Java 不同版本,比如 8、13、17、21,在内存结构和垃圾回收上有什么变化?
  9. 聚簇索引和非聚簇索引的区别是什么?
  10. 对一条 SQL 执行 explain 后,你会关注哪些信息?
  11. 你在滴滴主要是做取数 Agent 吗?
  12. 你说的分析是指数据分析?
  13. 生产环境 GC 这块,你还涉及稳定性和执行分级吗?
  14. 比如分析昨天的 GMV 数据、按人口划分,是这个 Agent 做的吗?
  15. 你了解哪些 Agent 相关技术?
  16. 什么是 ReAct?你接触过 LangChain、LangGraph 这些框架,或者阿里相关的 AI 框架吗?
  17. 你在滴滴具体负责什么?
  18. 讲讲 GC 治理的过程吧。
  19. CSV 读取和浏览功能比较耗内存,还有加载两次的问题,对吗?
  20. 数据资产批量查询,我理解是调用下游接口,用线程池批量执行,再合并结果,对吗?
  21. 那些接口都是不同组提供的?
  22. 校园招聘智能助手是你自己做的项目吗?里面也用了 AI 辅助开发?讲讲这个项目。
  23. 这个项目是你自己练手的,还是课题?
  24. 里面的多 Agent 内核是什么意思?
  25. 这个项目是中心化部署吗?
  26. 也就是说,它不是客户端,而是服务端?用户向服务器提交求职请求,服务器收集信息后再返回?
  27. 渠道抽象、三重分类、系统命令、关键词打分、拦截消息这些描述,是 AI 总结出来的?
  28. 你讲讲对 Spring 事件总线的理解?
  29. 事件总线和发布订阅模式、消息队列有什么区别?
  30. 电商优惠券平台也是个人练手项目?
  31. 你在滴滴的实习偏中台和基础,业务性不够强,你怎么看?后续做研发,你对哪方面更感兴趣?
  32. 现在 AI 发展越来越快,对程序员这一行影响很大,你怎么看程序员行业未来的发展?
  33. 现在纯写代码的部分,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 推迟,掩盖的是放大倍数。