一次诡异的 Node.js 内存爆炸排查:从 7GB OOM 到 70MB 的根因之旅

举报
yd_242888859 发表于 2026/10/10 19:38:27 2026/10/10
【摘要】 问题背景一个普通的 Node/Express 应用(内含若干自动化子任务),本地用 Node 跑一切正常:内存稳定在 100MB 左右,端口监听、接口响应全部正常。但换到 Bun 运行时(或者打包成 bundle 后在内存受限的容器里跑),现象完全变了样:进程 RSS 疯涨到 6~7GB,直接被容器 OOM Kill换个环境又变成 CPU 100% 死循环,但内存不动、日志一行不输出崩溃表现...

问题背景

一个普通的 Node/Express 应用(内含若干自动化子任务),本地用 Node 跑一切正常:内存稳定在 100MB 左右,端口监听、接口响应全部正常。

但换到 Bun 运行时(或者打包成 bundle 后在内存受限的容器里跑),现象完全变了样:

  • 进程 RSS 疯涨到 6~7GB,直接被容器 OOM Kill

  • 换个环境又变成 CPU 100% 死循环,但内存不动、日志一行不输出

  • 崩溃表现随环境漂移,没有任何一致性

排查过程:三个假设,逐一证伪

假设一:bundle 太大导致内存高?

对比测试:同一个入口,用 Node 跑 RSS 只有 106MB,用 Bun 跑 VSZ 飙到 19GB。排除打包问题——是运行时差异。

假设二:Bun + Express 本身有 bug?

写了个 10 行的最小 Express 服务用 Bun 跑:内存 58MB,稳如老狗。排除通用性问题——是这份代码特有的。

假设三:混淆代码的反调试逻辑不兼容 JSC?

这份代码经过 JavaScript Obfuscator 混淆,里面藏着几类典型的「防篡改」机关。V8(Node)和 JavaScriptCore(Bun)对它们的行为差异,就是问题的根源:

  1. 自毁死循环:字符串解密器里有个循环不断往数组 push 随机数再重置边界,在某些求值路径下永远不收敛——Node 的优化路径恰好跳过了它,Bun 没有

  2. 校验和旋转循环:while(true) 里用 parseInt 拼出一串魔法数求和,和目标值相等才 break。依赖浮点取整的微妙行为,JSC 下永远算不出目标值 → 100% CPU 空转

  3. Function('return this') 全局对象探测:沙箱环境拦截后会 fallback 到 window,Node 里 window 未定义直接 ReferenceError,反而掩盖了真实错误

解决方案:不是对抗,是绕开

逐行硬啃混淆代码效率太低,最终用 webcrack 一键反混淆:

npx webcrack index.js -o ./deob

95575 字节的混淆代码 → 595 行可读的干净代码。所有反调试机关被剥除,字符串还原成明文,函数名恢复语义。反混淆后的代码在 Bun 下运行:内存 71MB,一切功能正常。

复盘:三条可复用的经验

  1. 「换个运行时就崩」先查运行时差异,别急着怀疑自己的代码。V8 和 JSC 在边界行为(整数溢出、浮点取整、原型链探测)上的差异,会让混淆/依赖 hack 的代码表现完全不同。

  2. 最小化隔离测试是定位利器。「官方运行时 + 最小 Express」跑通,立刻把问题范围从「平台/依赖」缩到「这份代码」;再二分注释掉可疑代码段,几轮就锁定了混淆 prelude。

  3. 对混淆代码,反混淆优先于打补丁。我在反混淆前尝试了固化字符串表、逐个删除死循环等手动补丁,每次修好一个崩溃点就冒出下一个(语法坏了、递归栈溢出、undefined 调用链……),折腾近十轮。webcrack 30 秒解决战斗——工具存在就先用工具。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。