杭州某公司后端一面:GC 治理、锁优化与 TCP 丢包
- 轮次
- 一面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 讲讲你实习实际做的工作吧?
- 具体的 GC 治理主要是做啥的?为什么会引起 GC 压力,怎么解决的?
- 你之前在中国移动的实习要讲吗?
- HBase 详单查询是怎么优化的?具体思路是什么?
- 锁你熟不熟?如果要优化锁的开销,一般有什么办法?
- 一般怎么样才能减少锁的长期占用?
- 除了降低锁粒度和异步化,还有其他优化锁开销的办法吗?
- 计算机网络你熟不熟?TCP 协议你知道多少?
- TCP 怎么发现丢包?
- 你们书上或者老师讲的还是三个重复 ACK 吗?老师没讲过 SACK(选择性确认)吗?
《参考解析》
GC 治理怎么做:先定位再动手,第一步是拿数据——看 GC 日志和监控区分是 Young GC 频繁(本质是分配速率太高)还是 Full GC 频繁(老年代增长快、对象晋升过早或存在泄漏),必要时 dump 堆看占比最大的对象类型。真正压得住 GC 的是减少垃圾产生:循环里拼字符串、无意义的装箱拆箱、滥用 Stream 和正则、日志没用占位符,这些都会成倍制造临时对象;能复用的缓冲区与对象就复用,能批量处理就别一条条来,DTO 转换别层层深拷贝,大对象尽量缩短生命周期避免直接进老年代。参数调整是最后一步:根据存活对象规模估算堆大小与新生代比例,按停顿目标选收集器(大堆低延迟场景用 G1 或 ZGC),再调晋升阈值和 GC 触发阈值——堆加大只能降低频率,救不了泄漏。面试官几乎一定会追问”你怎么证明改完有效”,所以要能给出同流量下的 GC 次数、停顿时间和 TP99 的前后对比。
锁开销怎么优化:先分清是”锁争用严重”还是”锁本身的开销大”,两条路的解法不同。降低争用:缩小临界区(把日志、IO、RPC 挪到锁外)、降低锁粒度(分段锁、按 key 哈希分桶、ConcurrentHashMap 式的分片计数)、读写分离(读多写少用读写锁或不可变快照)、用无锁或乐观方案(CAS、原子类、LongAdder 把热点打散)、把多次小操作合并成一次批量加锁、用单线程串行队列替代锁(Actor、环形队列的思路)。缩短持有时间:临界区内不做阻塞调用、不分配大对象、不抛异常,多把锁按固定顺序获取防死锁,同时注意锁粒度过细会带来上下文切换开销。终极答案是”能不能不加锁”——把共享可变状态改成线程各持一份、最后再合并,收益往往比调锁大得多。异步化的本质也是这个思路:把串行的资源等待改成回调或事件驱动,锁自然就少了。
TCP 怎么发现丢包:两条机制。第一条是超时重传:发送方为每个报文维护重传定时器,RTO 由平滑 RTT 和 RTT 方差估计出来,超时后重发并指数退避,好处是兜底、坏处是慢。第二条是快速重传:接收方收到乱序报文时会重复确认最后一个连续收到的字节,发送方连收三个重复 ACK 就判断中间缺了一段,不等超时立刻重传,这就是课本上经典的”三个重复 ACK”。它的盲区是窗口尾部丢包——后面没有足够的新报文去触发重复 ACK,只能退回 RTO;SACK(选择性确认)就是补这个洞:接收方在 TCP 选项里把已经收到的离散区间明确列出来,发送方据此只重传真正缺失的段,避免一个丢包导致整个窗口重传。再往深一层,重复 ACK 也可能由网络乱序引起,所以协议栈会结合乱序计数、是否已重传等信息判断,避免伪重传;遇到面试官问”还有别的信号吗”,可以补上 DSACK、以及拥塞控制里把丢包当拥塞信号的经典假设在无线链路上并不成立,这正是 BBR 改看带宽与时延的原因。
HBase 详单查询优化:核心思路是不让查询走全表扫描,围绕 rowkey 设计做文章。第一步是把最常用的过滤维度按基数从高到低拼进 rowkey 前缀,让查询能用 startRow/stopRow 变成范围扫描,同时避免热点——单调递增的前缀会让写入全压在同一个 Region,常用的解法是加盐(前缀哈希打散)或反转字段。第二步是减少单次扫描的数据量:明确指定列族和列(列族不要建太多,因为按列族存储和刷写)、控制 setCaching 与 setBatch 平衡 RTT 和内存、只取需要的版本数、必要时加 Filter 在服务端过滤而不是拉回客户端。第三步是换思路:高频的复杂条件查询用二级索引方案(Phoenix 或自建索引表)、把明细预聚合成宽表或走 ES 做检索,HBase 只存明细主键。最后要注意 region 分裂与预分区、以及大 row 和大 cell 对读写放大的影响。