百度后端开发岗面经 12:协议栈、JVM 排查与内存池拷打
- 轮次
- 多轮面试合集
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- Agent 可观测性中的 Trace 指什么?
- 请介绍 TCP 三次握手和四次挥手
- 常见的 HTTP 状态码有哪些?
- 请介绍 HTTPS 握手过程
- 线程、协程和进程有什么区别?
- 请介绍 Go 的 GMP 调度模型
- Go 如何实现抢占式调度?
- Go 协程之间如何通信?
- Java HashMap 的底层实现是什么?
- Java 中使用过哪些线程锁?
- 如何排查 JVM 问题?
- 红黑树的原理是什么?
- 红黑树和跳表有什么区别?
- Redis ZSet 与跳表是如何实现的?
- Docker 的实现原理是什么,常用命令有哪些?
- Kubernetes 的核心组件有哪些?
- Agent 项目是做什么的?
- RPC 了解吗?
- 请求方法有哪些?
- 请求参数放在哪里(三种)?
- 设计一个接口:下单接口,怎么设计请求方法和请求参数,请求过程如何?
- 引入 Redis、消息队列是为了什么?
- 输入 URL 到浏览器渲染出页面,过程如何?
- MySQL 如何解决 SQL 性能差的问题,可能的原因是什么?追问:如何解决?
- 索引失效是什么?
- 你的 MySQL 是 Docker run 的对吧?
- 那个请求到了服务器以后,镜像容器运行时内部是不是也有一个流程?
- defer 原理是什么,常见应用场景是什么?追问:使用规范是什么?追问:为什么要这么使用?
- 用过什么 Linux 命令?
- 如何查看 CPU、内存、磁盘使用情况?
- 设计文章点赞排行榜
- 开发一个功能:文件打开、读、写到另一个文件、关闭,这个过程会有什么问题?
- 手撕:最长无重复子串
- Redis 的 hash 的底层原理是什么?
- 对 Git 有了解吗?
- Docker 的镜像分层是什么?
- 手撕:3 个协程交替打印 1~100
- 自我介绍
- 对于 malloc 的三个痛点,你是在哪里去了一把锁?
- 内存池为啥要三层结构,如果变成两层或者四层会发生什么?
- 你的内存申请是每次多少为一片?
- 我把你的中心缓存踢掉会发生啥?
- 你现在有一个指针,如何找到它的 span 呢?
- 你说三层基数树,那好,为啥不用一层呢?一层不是更快吗?
- 你的混合尺寸测试,在 Windows 和 Linux 下性能不一样,这底层是为什么?(原帖在此截断)
《参考解析》
1. 线程、协程与进程。进程是资源分配的单位,拥有独立地址空间;线程是 CPU 调度的单位,同进程内线程共享内存,切换要陷入内核、保存寄存器与栈,成本在微秒级;协程是用户态的轻量线程,切换只发生在用户态、不用进内核,栈可以很小并按需增长,所以能开到几十万个。代价是协程必须协作式让出,一个协程里写了阻塞式系统调用,整个线程上的其他协程就都被卡住。
2. Go 的 GMP 与抢占式调度。G 是 goroutine,M 是内核线程,P 是逻辑处理器,持有一个本地可运行队列。M 必须绑定一个 P 才能执行 G,本地队列空了会去全局队列或其他 P 那里偷(work stealing),这样把调度开销摊到多个核上。抢占在 Go 1.14 之后是基于信号的异步抢占:以前只在函数调用点检查栈增长标记,死循环里插不进调度点;现在运行超过 10ms 就发 SIGURG,让目标 M 在安全点停下来重新排队。
3. 红黑树与跳表的选择。红黑树是自平衡二叉搜索树,通过颜色约束保证最长路径不超过最短路径的两倍,查找、插入、删除都是 O(log n);跳表用多层链表加随机层高达到期望 O(log n),实现简单得多、范围查询友好(顺序遍历底层链表即可),代价是额外指针空间和随机性带来的常数波动。Redis 的 ZSet 在元素少时用 ziplist,元素多或成员较大时用跳表 + 字典:字典负责 O(1) 按成员查分数,跳表负责按分数排序和范围查询——之所以不用红黑树,除了实现简单,还因为跳表的区间操作代码短得多。
4. 排查 JVM 问题。先分层定位:jps 找到进程,jstat -gcutil 看 GC 频率与停顿,jstack 抓线程栈看有没有死锁或大量 BLOCKED、WAITING,jmap -histo 看对象直方图、jmap -dump 导出堆再用 MAT 分析支配树。CPU 飙高时常用组合是 top -Hp <pid> 找出最烫的线程 id,转成十六进制去 jstack 输出里搜 nid,直接定位到那行代码。内存持续上涨要看是老年代泄漏还是缓存没有上限,别一上来就调大堆。
5. 索引失效的常见场景。在索引列上做函数或运算(WHERE DATE(create_time) = ?)、隐式类型转换(字符串列传数字)、LIKE '%xx' 前置通配、OR 连接的条件里有一侧没索引、联合索引不满足最左前缀、!= 与 NOT IN 导致优化器放弃索引、以及在区分度极低的列上建索引——最后一种其实不是「失效」,是优化器算出来走全表更便宜。排查手段就是 EXPLAIN 看 key 和 rows,别凭感觉判断。
6. 内存池项目的连环追问怎么接。这类问题考的是「你知不知道自己为什么这么设计」。malloc 的三个痛点是锁竞争、内存碎片、频繁向 OS 申请,所以 tcmalloc 用线程本地缓存把绝大部分分配留在用户态(不存在锁竞争),用中心缓存和小对象分级(size class)控制碎片,用页堆按 span 批量向 OS 要内存。三层结构(线程缓存 → 中心缓存 → 页堆)是「快路径无锁、慢路径集中管理」的取舍:减到两层就没有了免锁的本地缓存,加到四层则多一级跨线程搬运、命中率提升有限却增加复杂度。为什么用基数树而不是一层数组,答案是地址空间太大、一层映射表本身就会把内存吃光,三层基数树用稀疏结构只映射实际用到的地址段。被问到「把中心缓存踢掉会怎样」时,要立刻答出后果:线程本地缓存耗尽后只能直接向页堆要,锁竞争回到全局,吞吐会明显下降。