携程后端面经合集:HashMap、缓存一致性与 JVM 排查
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
2025 年 9 月 21 日至 22 日:集合、泛型与限流
- HashMap 为什么在链表较长时转成红黑树,树化需要哪些条件?
- MVCC 能解决哪些并发读取问题,快照读与当前读混用时要注意什么?
- 深分页查询怎样优化,多级缓存怎样设计?
- 线程池达到核心线程数后,新任务怎样处理?
- Java 泛型如何实现,TypeReference 怎样保留并解析类型信息?
- 泛型通配符 extends 与 super 分别适合什么场景?
- CMS 与 G1 的适用场景、算法和执行流程有什么区别?
- Java 常用集合有哪些,HashMap 的 put 完整流程是什么?
- 红黑树怎样查找,为什么能控制查询复杂度?
- MySQL 四个隔离级别分别处理什么问题,幻读和不可重复读有什么区别?
- Redis 有哪些数据类型,热 Key 和大 Key 如何处理?
- Redis Cluster 查询怎样路由?
- 怎样实现本地按 IP 限流,要求一秒内超过 10 次访问返回 false?
- 对带嵌套的 k[encoded_string] 字符串,如何展开重复内容?
- RAG、Lua 脚本、全局唯一编号和 OpenFeign 分别怎样用在项目里?
- Spring Boot 自动配置怎样生效?
- 线程池使用 ThreadLocal 时有哪些内存风险,怎样清理?
- 两阶段提交与三阶段提交有什么区别?
- HTTP 和 TCP 有什么关系?
- ReentrantLock 与 synchronized 有哪些区别?
2025 年 9 月 18 日至 19 日:项目与消息系统
- 项目主要开发什么功能,开发和测试的分工是什么?
- 两个模块怎样通信,Redis 在其中用来做什么?
- ZSet 底层怎样组织数据,Redis 怎样判断和清理过期键?
- equals 与 hashCode 有什么关系?
- 除 new 以外,还有哪些创建对象的途径?
- JSON 序列化与反序列化怎样工作,反序列化有哪些风险?
- 线程同步有哪些方式,CAS 有哪些局限?
- 如何了解 JDK 版本变化,遇到过哪些 OOM?
- 接口幂等、消息可靠性和消息顺序分别怎样保证?
- 数组与链表有什么区别,JDK 8 的 HashMap 有哪些变化?
- 什么是内存访问的有序性,怎样建立线程间的顺序约束?
- 能否用 String 对象加锁,有什么风险?
- 对象创建、类加载与双亲委派分别经过哪些过程?
- 可重复读与 MVCC 有什么关系?
- SELECT * 可能带来哪些额外成本?
- 用哈希表保存百万条数据有什么优点与代价?
- Redis Hash 与 Java HashMap 有哪些差异?
- 面向用户请求的业务线程池怎样设置参数?
- Reactor I/O 模型是什么?
- 最有收获的项目是什么,怎样解决困难和保护数据库数据?
- 怎样使用辅助编程工具,最近在学习什么?
- 遇到死锁时如何排查,如何避免重复发生?
- 用过哪些设计模式,单例的常见实现有什么区别?
- JVM 堆和线程栈各保存什么,和 Java 内存模型有什么区别?
- Redis List、Set、Sorted Set 分别适合什么场景?
- 如何自定义 Spring Boot 自动配置?
2025 年 9 月 15 日至 16 日:锁、缓存与分库分表
- 乐观锁与悲观锁有什么区别?
- 怎样基于 AQS 实现可重入的公平锁?
- 如果设计 Tomcat 的线程池,需要考虑哪些因素?
- 消息队列怎样避免重复消费,延迟消息怎样使用?
- 缓存穿透和击穿有什么区别,各有哪些处理办法?
- 布隆过滤器怎样工作,有哪些局限?
- 更新数据库与删除缓存不是原子操作时,怎样处理一致性?
- 分布式编号有哪些生成方式?
- binlog 有什么作用?
- 分库分表的依据是什么,为什么不能只根据行数作决定?
- 项目使用什么垃圾回收器,选择理由是什么?
- G1 的 Region 有哪些角色,Region 大小怎样确定?
- 对 ZGC 有哪些了解?
- 线程池有哪些拒绝策略,业务中为什么选择其中一种?
- 父进程、子进程和孙进程共用一个线程池,会遇到什么问题?
2025 年 9 月 13 日及更早场次:线上排查与业务设计
- SQL 已使用索引却依然很慢,可能有哪些原因?
- B+ 树有哪些优势和代价?
- 限流怎样实现,怎样进行压力测试?
- Elasticsearch 的数据存储与检索结构是什么?
- volatile 起什么作用,为什么会有指令重排序?
- 除双重检查锁外,有哪些更简单的单例实现?
- 对象是否一定分配在堆上,对象头保存哪些信息?
- 怎样理解 synchronized 的锁状态变化?
- 生产环境 CPU 飙高时怎样排查?
- 项目中的业务目标和技术选择怎样权衡?
- 秒杀系统怎样设计,数据库层面如何避免超卖?
- Full GC 可能由什么触发,怎样分析?
- 水平分表后还需要解决哪些问题?
- 接口自动化验证除了检查响应报文,还能检查什么?
- 哈希算法和 HashMap 的工作方式是什么?
- CMS 与 G1 的回收过程有什么区别?
《参考解析》
HashMap 的阈值不是“8 个元素一定树化”
JDK 8 的实现还会检查数组容量。容量较小时优先扩容,让哈希冲突有机会分散;满足容量条件后才考虑树化。树节点有额外空间成本,因此短链表不值得付出这份开销。树化、拆分时退化使用不同阈值,也减少了形态频繁切换。回答时应把链表长度、数组容量和扩容过程一起讲清。
字符串解码用栈保存外层状态
逐字符扫描,连续数字组成重复次数;遇到左括号时,把当前字符串与次数入栈,并开始新的内层片段;遇到右括号时,取回外层字符串,把内层内容重复指定次数后拼接回去。需要处理多位数和多层嵌套。复杂度还受展开后输出长度影响,不能只按输入长度计算。
严格的一秒限流先明确窗口
如果要求任意连续一秒最多放行 10 次,可为每个 IP 保存已通过请求的时间队列。请求到达时移除窗口外的时间戳,剩余不足 10 个才放行并入队。清理、判断和入队必须作为一个受保护的操作。固定自然秒计数实现更简单,但在秒边界附近可能短时间通过两倍额度,它不等价于滑动窗口。
缓存一致性不能只背“双删”
先明确允许读到旧值多久。常见流程是事务提交后删缓存,删除失败进入可重试记录;但旧查询也可能在删除后把旧值写回缓存。可根据业务采用版本号校验、串行化同一键的更新、变更订阅和过期时间等办法约束这个窗口。延迟再删一次只是缩小特定竞态,不自动等于强一致。
索引命中不是优化结束
检查实际扫描行数、返回行数和回表次数,再看排序、临时结果、锁等待与网络返回量。一个条件能用索引,不代表选择性足够高,也不代表覆盖排序和所需列。不要为了让执行计划显示某个索引,就忽略整条查询的时间和资源消耗。
内存术语别混在一起
JVM 运行时区域描述堆、栈等存储组织;Java 内存模型讨论线程之间的可见性、原子性和先行发生关系。对象引用作为局部变量时可以位于线程栈,对象通常分配在堆。JIT 可能通过逃逸分析和标量替换消除某些分配,因此“局部对象一定完整地放在栈上”也不准确。
TypeReference 保留的是类型声明信息
普通泛型对象不能直接告诉你运行时的全部类型参数,但匿名子类可以在泛型父类签名中保留诸如 List
布隆过滤器不适合随意清位
多个元素可能映射到相同的位。查询命中的元素可能并不存在,但正常插入且未损坏、未误删的过滤器不会漏掉已经插入的元素。删除一个元素时直接清位,会误伤其他元素;要支持过期,可考虑分时间窗口轮换过滤器,或使用带计数的变体,并明确内存和并发代价。
进程不能直接共用一个 Java 线程池对象
不同进程有独立地址空间。若题目实际指父任务提交子任务,那么重点是线程池饥饿:工作线程全部阻塞等待子任务,而子任务又排在同一个池里,就可能无人执行。若确实是跨进程协作,应讨论进程间通信和任务分发,而不是把普通线程池当作共享对象。