This website requires JavaScript.

记一次内存泄漏

2019.12.15 08:52 字数 2582 喜欢 5 评论 4

这是笔者在 Chrome 78 版本时发现的问题,Chrome 79 中已经没有此问题,后文会提及

某日,在对笔者的 BLOG 性能调优的时候,发现一个比较奇怪的现象,Chrome 某个进程占用的内存越来越高:

活动监视器

根据以往的经验,这可能是某个 Tab 页发生了内存泄漏(Tab 进程和扩展程序进程在活动监视器中都是以 Google Chrome Helper Renderer 显示)。

在打开 Chrome 任务管理器查看明细时:

任务管理

正常情况下:

任务管理

该 Tab 页运行一段时间后,占用内存 1.1G,而正常情况下,该标签页占用的内存,应不到 100MB。

内存泄漏?

在打开的这个任务管理器中,有两个重要的参考数据:

  • 内存占用空间:如果该值正在增大,则表示正在创建 DOM 节点。
  • JavaScript 使用内存。笔者们关心的是实际大小,该值表示页面上的可到达对象正在使用的内存量。如果此数字在增大,要么是正在创建新对象,要么是现有对象正在增长。

在上图中,该标签页的内存占用达到 1.1 G 多,JavaScript 使用内存的实际大小相差无几。对比过后,可以得知在这段时间内,该 Tab 页创建了大量的 DOM。

接着,在该页面没有进行任何操作时,笔者借助于 DevTools Performance 面板记录了一段时间的内存使用情况(为了更易于观察,在开始时间、3s、7s、10s,各进行了一次强制垃圾回收):

Performance

图中 HEAD 图表(蓝色框)表示 JS Heap,与下方的 Counter pane 中的 JS Heap 变化一致,但是在这段时间内 Nodes 数量并没有发生变化。

在 JS Heap 到最高点时,都会快速下降(除了手动触发 GC,途中快速下降),自动触发了 GC:

Performance

此外,在大概每过 30ms,会循环执行相同的两组函数,导致内存增加:

Performance

将其中一组放大时:

Performance

于是,笔者大概确认了问题所在:每隔 30ms,会跑两个一样 Task,调用函数 update。它会造成内存增加,但是不会增加 DOM 节点。

查阅相关 源码 后,它主要是 canvas 循环实现 Loading 效果:

loading

Chrome 79 版本

当笔者在苦恼如何该解决问题时,Chrome 发布了 79 版本,且在 79 版本中已经解决了此类问题。

Chrome 79 版本 Performance 录制的内存使用与 Chrome 78 版本无异:

chrome-79-performance.png

但是在 Chrome 79 版本时(没有开启 Tab Freeze),内存处于正常状态:

任务管理器

接着,笔者找到本地安装 Chrome 中 V8 版本:

version

以及在 V8 7.9 版本发布的文章中,发现有关于 OSR caching 的内容。

OSR caching

此段翻译于: https://v8.dev/blog/v8-release-79#osr-ca...

当 V8 识别出某些函数很“热”时,它会将其标记用于下次调用时优化。当这些函数再次执行时,V8 使用优化后的编译器编译该函数,并且在接下来的调用中,都使用该编译后的代码。然而,对于具有长时间循环的函数来说,这还不够。V8 使用一个被称之为「on-stack replacement(OSR)」的技术,为当前执行的函数安装优化后的代码。它可以让我们在第一次执行函数时,就使用优化后的代码。

如果该函数第二次执行,它很可能被再一次 OSR。在 V8 7.9 版本前,我们需要重新优化该函数以进行 OSR。从 7.9 版本后,我们添加了 OSR 缓存,保留用于 OSR 替换的优化代码,该段代码由循环头(在 OSR 化后的函数中,它被用于入口点)确认。这使得一些最高性能基准提高了 5%-18%。

由此,笔者推测正是由于这个优化,才在 79 版本时内存正常。

至于验证,笔者也没找到一个方式。。。。

Contact

微信公众号

微信

相关推荐

暂无推荐文章