华为面试挂了!48 核 CPU 瞬间飙到 100%,排查不出死锁,面试官:你确定你是 Java 专家?
写在开头
前两天有个兄弟说去面了华为的高级架构岗,本来前面聊得顺风顺水,结果终面被一个排查题直接干碎了。
面试官原话是:“生产环境有一台 48 核的机器,本来跑得好好的,突然 CPU 瞬间全飙到 100%。但是看日志,没有任何死锁,你觉得是啥情况?”
这兄弟平时也是个带组的,随口就来:“估计是有人写了死循环吧,或者是内存里在做大量数据排序?”
面试官直接冷脸了:“如果只能想到死循环,那这台 48 核的服务器算白瞎了。你这架构师水分有点大啊。”
今天 Fox 就跟兄弟们把这道题彻底盘透。当“高并发”、“多核”、“CPU满载”这三个词同时出现时,别总盯着死循环,里面藏着的都是底层原理的修罗场。
一、 别再让死锁背锅了
一提到 CPU 飙升,很多人脑子里条件反射就是“死锁”。但这绝对是八股文背多了产生的错觉。
记住一个常识:死锁是互相等,既然是“等”,线程就是阻塞状态(Blocked/Waiting)。大家都罢工不干活了,CPU 闲得抠脚,怎么可能 100% 满载?
CPU 满载说明什么?说明这 48 个核心正在被发疯一样地疯狂调用。面试官特意强调“没有死锁”,其实就是在考你:除了死循环,还有哪些隐蔽的机制,会让系统产生这种“假忙碌”?
二、 48 核满载的 3 大幕后黑手
在单核或 4 核机器上,很多性能问题根本暴露不出来,但到了 48 核这种神仙环境,微小的争抢都会被无限放大。核心看这三个场景:
真凶一:CAS 自旋锁引发的“总线风暴”
平时写并发,大家肯定最爱用 AtomicInteger 或者 ReentrantLock 的非公平模式,图它轻量级。它们底层都是 CAS(Compare And Swap)。 CAS 的本质是啥?竞争失败就疯狂 while(true) 重试。在 4 核机器上,自旋一下无所谓。但你想想,在 48 核机器上,如果遇到极端高并发争抢,可能同一时刻有 40 多个核心都在疯狂自旋!代码连一行有用的业务逻辑都没跑,服务器算力全被这种“无能狂怒”消耗光了。
真凶二:硬件级的坑 —— 伪共享(False Sharing)
这个坑,90% 没做过底层优化的开发可能都没听过。CPU 读数据不是一个字节一个字节读的,而是按“缓存行(Cache Line,通常 64 字节)”来读的。 假设你有两个完全不相干的 volatile 变量 A 和 B,碰巧挨在一起,被加载到了同一个缓存行里。核心 1 修改 A,核心 2 只是想读 B,但因为在同一行,底层硬件层面(MESI 协议)就会强制核心 2 的缓存失效,必须重新去主存拉取。 结果就是,48 个核心为了保证数据一致性,一直在底层总线上发消息同步,造成总线拥堵,CPU 周期全浪费在底层的通信上了。
真凶三:被忽视的 SafePoint(安全点)
发现 CPU 100%,系统还有卡顿,但你去查 GC 日志,发现根本没回收多少垃圾。这时候就要怀疑 SafePoint 了。 假设代码里有个巨大的循环(比如 for(long i=0; i<Integer.MAX_VALUE; i++)),而且循环体里全是基本运算,没有方法调用。JVM 可能会认为这不是一个 SafePoint。 麻烦来了:此时如果 JVM 想要触发 GC,或者你想打个 Heap Dump,必须让所有线程停下来(STW)。结果其他 47 个核心早早就停在安全点了,全都挂起干等,就为了等这一个长循环跑完!这种资源的白白浪费,也会在监控上表现出剧烈的异常波动。
三、 怎么排查?实操上强度
光懂原理不够,面试官看重的是落地。遇到这种灵异事件,按照下面这三步走,别乱了阵脚:
第一步:抓出罪魁祸首
别上来就 jstack 盲猜,先找元凶:
# 1. 拿到进程号 jps -l # 2. 找出具体是哪个线程吃掉了 CPU(-Hp 必带) top -Hp <PID> # 3. 把线程 ID 转成 16 进制记下来,再去日志里对 printf "%x\n" <TID>第二步:祭出 Arthas 火焰图
现在的生产环境,强烈建议用 Arthas(阿尔萨斯)。跑一把 profiler start,直接生成一张 CPU 火焰图。哪个方法对应的方块越长,说明它霸占 CPU 的时间越多,一眼就能看到是不是某个自旋锁在作妖。
第三步:对症下药
- 针对 CAS 争抢:把 AtomicLong 换成 LongAdder,把单点热点分散到多个 Cell 里去(典型的空间换时间)。
- 针对伪共享:祭出 Java 8 的 @Contended 注解,强行把这两个变量用空白字节隔开,不在一个缓存行就行了。
四、 面试标准答案模板(直接背诵)
把这段逻辑吃透,下次面试直接丢给面试官:
“如果 48 核 CPU 飙升且无死锁,我首先会用 top -Hp 结合 Arthas 火焰图抓取高耗能线程,排除业务上的死循环。
但在这个量级的硬件下,大概率是底层资源争抢。我会重点看三个方向:
第一,看火焰图是否大量时间耗在底层原子指令上,排查并发过高导致的 CAS 自旋风暴;
第二,排查 volatile 变量分布,看是否存在伪共享导致的缓存行频繁失效;
第三,检查 GC 日志和长循环逻辑,确认是否有缺乏 SafePoint 的代码拖慢了全局 STW,导致大量线程无效挂起等待。
找到瓶颈后,再通过引入 LongAdder、添加防伪共享注解等针对性手段进行优化。”
写在最后
技术上的碾压,往往不是因为对方懂得多酷炫的新框架,而是别人真正在高压环境下踩过这些底层的坑。单机跑得溜,到了多核集群可能处处是雷。对底层机制多一分敬畏,线上出事故时手就少抖一分。
如果你在火焰图抓取中,发现大量 CPU 时间全耗在了 System.arraycopy 上,你觉得业务代码里可能在做什么骚操作?
