坏蛋Dan
知乎@坏蛋Dan
发布时间:2025.8.18

前言

最近(并不是)收到社区反馈:win10打不开QQ小程序开发者工具,就是点击了没反应那种。这种问题比较麻烦,一般是需要去看crash report的,但nwjs(实际上是谷歌的minidump_stackwalk)这块对window系统太不友好了,搞半天都搞不好。

这里发现另一个相同功能的,用rust写的:rust-minidump,安装倒是非常快,然并卵,它貌似只支持Firefox的minidump。。。

扯远了,总之看crash report这块没什么进展,只能怀着期望去nwjs的issue里找。 没想到真让我找到了:Nw.js sample app's window is not opened on Windows 10。 发现原来是缺少了vc++的runtime:Visual C++ Redistributable。 然后和社区反馈用户沟通了下,安装了这个runtime之后真的正常了!

那么围绕着这个问题的版本升级就开始了。。。 找版本、解决兼容问题等略过。。。

我们来回归主题:内存泄漏。

问题

在升级过程中明显感知到内存占用在上升,表现为调试面板时常崩溃,得重启。 但我以为这是常态,在生产环境下应该可以减少这方面的问题也就没理。

然而在构建安装包并安装之后。。。 一开始各方面测试还算正常,直到测试到小程序代码改动引起重新编译这个功能点时,一两次触发之后就崩溃了!!!并且是稳定的崩溃,不是偶然性的。 这还得了,赶紧打开任务管理器看什么情况,然后就看到下面这个:

好家伙,这个线程吃的这么肥!

原因

在重复测试了几次之后,确定是内存问题且是其中一部分有问题。那么结合前面调试面板多次,可以大胆的怀疑是调试面板即devtools的问题。

devtools调试面板

这里的调试面板指的是这个:

简单的说下这个devtools:

  1. nwjs是基于大家都看过源码的chromium + nodejs实现的
  2. chromium的调试面板前端部分是devtools-frontend
  3. 开发者工具的调试面板是基于devtools-frontend + 自己注入的代码(实现通信等)/拓展(自定义面板如audits)实现的。如果你对这块感兴趣,可以看下官方文档:extend-devtools

我们今天的主角就来自于我们注入的代码。

基于performance确定是内存泄漏

虽然前面我们通过任务管理器看到内存在膨胀,但可以的话我们还是专业点,通过performance来分析下内存泄漏。 这块很简单,因为我们明确引起内存膨胀的行为有点击【编译】按钮触发重新编译,所以我们只需要记录连续多次从点击按钮到编译完成这个过程即可。

可以清晰的看到内存并没有被释放,且通过截图两栏可以肯定上升时正好是编译的阶段。

对比下以前的版本:

可以发现内存是正常释放的。

基于memory面板分析内存泄漏的来源

performance很好用,但是它主要是用来分析代码耗时之类的性能问题,对于内存泄漏就有些不对口了。所以我们需要借助另一个工具:memory

这里用先用堆快照看下啥玩意儿吃我这么多内存:

发现是devtools-frontend提供的sdk里的一段代码。 坏了,不会是devtools-frontend的问题吧?!是的话这次升级得回炉重造了! 然而逛遍GitHub也没找到相关issue,而且这次升级改动也主要是sdk相关的,貌似对方从cjs重构成esm了,所以有些东西它不往外暴露,需要我们通过其它手段去获取对应的api,所以有理由怀疑是代码导致的(实际上我还试了其它nw的版本,发现超过本次升级版本的都是这样)。

跟着快照给出的索引,我们在这里打上断点,看下是啥代码引起的创建这个对象:

然后重新编译:

嗯?!蛇皮调用栈一段带一段的,一看就不对劲。 相关代码:

递归等待拿到sdk用来手动开启networkdisabled cache

但理论上不会这么长,顶多一两次,然后。。。

原来是函数里报错导致一直被catch!!!

catch没有输出error,导致一直以为这里是正常的。。

孔子言:try而不catch(指不输出error),坏人也。

那么问题是否真的出在这呢?我们修复看看

其实这里还有个问题,就是bug引起创建的对象为什么没有被回收,实际上这个对象还被内部其它对象引用,所以导致了不会释放,这个也是可以通过memory快照里看到的。

修复

这里也是api的调整导致的报错,所以在devtools-frontend里找到相关的api替换上即可,这里就不贴代码了。

修复后发现内存回收正常了:

总结

这种bug处理起来还是很有成就感的,虽然只是一个小小的内存泄漏。devtools的几个面板,关键时刻真能救人!