likes
comments
collection
share

【electron】我是如何将窗口的内存从500M降低到132M的

作者站长头像
站长
· 阅读数 35

嘿嘿,有点标题党。但确是事实。这篇文章中,我使用new BrowswerWindow和window open分别创建4个窗口,显示相同的内容。前者4个窗口4个独立的渲染进程总的内存开销500MB左右,后者4个窗口共享一个渲染进程只需要100多MB。本篇文章就是介绍这两种方案的差异,这也是我为什么推荐在electron项目中使用window open而不是new BrowserWindow创建窗口的原因

前言

本篇文章研究通过 window.open 和 new BrowserWindow 两种方案创建新窗口内存开销、进程数量对比。

  • 操作系统:macOS Mojave
  • Electron 版本:8.5.1

本次实验所使用的前端代码在这里:web-cross-platform-application

electron 代码在这里:electron-app

内存分析方法

  • chrome dev tools memory.
  • process.memoryUsage
  • webFrame.getResourceUsage。electron 提供的一个 API,可以获取渲染进程缓存所使用的内存开销。同时 electron 还可以使用webFrame.clearCache清除缓存

在这次实践中,我发现使用 webFrame.getResourceUsage 是比较靠谱的,能够帮助我们获取渲染进程各个资源的缓存占用的内存开销情况,清理不再使用的缓存。

loadURL 和 loadFile

BrowserWindow可以通过loadURL或者loadFile加载窗口。一般情况下,通过new BrowserWindow创建的窗口,一个窗口对应一个render process

const mainWindow = new BrowserWindow({
  //...
});
// mainWindow.loadURL("http://localhost:3000/memory-usage");

mainWindow.loadFile("index.html"));

但是,通过 loadURL 加载的窗口,会额外多出一个占用内存很小的render process

【electron】我是如何将窗口的内存从500M降低到132M的

这里需要注意,在整个应用生命周期内,不管有多少个BrowswerWindow通过loadURL加载窗口,最多只会多出一个render process

【electron】我是如何将窗口的内存从500M降低到132M的

进程数量

通过 new BrowserWindow 创建新的窗口,每个窗口都有一个对应的render process。这些渲染进程都是独立的,没法共享内存。通过window.open打开同源窗口时,新窗口会在同一个渲染进程(即父窗口)中创建,也就是说,无论我们通过window.open打开多少个窗口,最终只有一个render process。因此通过window open打开的窗口,是可以共享内存的。所以子窗口是可以直接使用父窗口的变量等。可以参考electron文档

【electron】我是如何将窗口的内存从500M降低到132M的

new BrowserWindow

这里我们通过 new BrowserWindow 创建三个新的窗口,进程数量和内存开销如下:

【electron】我是如何将窗口的内存从500M降低到132M的

从图中看出,4 个窗口总共 4 个 render process,这 4 个 render process 所占的内存总和差不多 500MB

当我们关闭新创建的 3 个窗口,只保留最初的窗口,我们发现,最初窗口的 render process 内存几乎没啥波动

【electron】我是如何将窗口的内存从500M降低到132M的

window open

为了保证条件一致,在使用window open创建窗口前,我们先停止 electron 主进程,并重启。然后点击按钮创建三个新的窗口,进程数量和内存开销如下图:

【electron】我是如何将窗口的内存从500M降低到132M的

从图中看出,即使是打开了 4 个窗口,整个应用生命周期内只有一个 render process,而这个 render process 所占内存开销为132.9MB因此,在打开业务功能相同的窗口的情况下,window open 能节省不少内存开销

注意上图中的 images 数量是 18。这里,我们打开的窗口内容实际上都是一样的,每个窗口都是显示 6 张图片。理论上,不论打开多少个新窗口,render process 只会缓存 6 张图片,因为这 6 张图片的 url 都是一样的。但是为了区分 url 不一样的场景,我在打开窗口时,刻意加了 id,然后在图片链接后面加了个查询参数?id=id。所以这里打开 3 个窗口,就 18 个图片 url

【electron】我是如何将窗口的内存从500M降低到132M的

当我们关闭新创建的 3 个窗口时,只保留最初的窗口,我们发现,render process 的内存并没有释放。如下图所示:

【electron】我是如何将窗口的内存从500M降低到132M的

如果我们点击 清除webFrame缓存 按钮,我们会发现 render process 内存降下来了,但是相比于创建窗口前的内存,增加了大约 20MB,如下图:

【electron】我是如何将窗口的内存从500M降低到132M的

这 20MB 的内存波动是怎么来的?需要深入研究

小结

  • 当我们通过 new BrowserWindow 方式创建新窗口时,主进程会为每个窗口创建一个 render process,这些 render process 加起来的内存开销还是比较大的。当我们关闭这些新创建的窗口时,最初的窗口对应的 render process 所占的内存开销几乎没有波动

  • 当我们通过 window open 方式创建新窗口时,新窗口会在父窗口的渲染进程中创建,共用一个 render process,因此这些窗口是可以共享进程内存的。上面的实践证明,通过 window open 打开新的窗口,这些窗口的内存总开销比通过new BrowserWindow创建的内存总开销小很多。但是,当我们关闭其他新创建的窗口,只保留最初的父窗口,会发现父窗口的 render process 所占的内存并没有降下来。这是因为,新窗口中所有的请求,比如这里的图片请求、脚本请求,都会经过父窗口的 render process 进行缓存,这部分缓存的内存并不会随着新窗口的关闭而清除。因此,我们需要在窗口关闭时手动调用 webFrame.clearCache() 将这些缓存清除,render process 的内存也会降下来。但是,即使是调用了 webFrame.clearCache()清除缓存,render process 的内存还是比之前增加了大概 20MB,这说明还有资源没有被释放,这个还需要研究

反复打开关闭窗口,对内存的影响

在 electron 的 issue 中,找到不少描述反复打开关闭窗口,内存会持续增加或者内存泄漏的 issue,比如这个

【electron】我是如何将窗口的内存从500M降低到132M的

可以在 electron 的 issue 中搜索 memory leak查看相关的 issue

在我的实践中,通过 new BrowserWindow打开的窗口,即使是反复打开关闭,内存的波动比较小,可以忽略不计。

但是,通过 window open 反复打开关闭的窗口,不论打开关闭多少次,调用 webFrame.clearCache()清除缓存后,相比最开始的状态,内存依然增加了 20MB

比如,下面是 render process 最开始的状态:

【electron】我是如何将窗口的内存从500M降低到132M的

点击按钮,反复打开关闭弹窗,然后点击清除 webFrame 缓存按钮,内存短时间内稳定在 50MB,如下所示:

【electron】我是如何将窗口的内存从500M降低到132M的

大概过了几十秒,内存又恢复了之前的 30MB 左右

【electron】我是如何将窗口的内存从500M降低到132M的

因此,可以猜测,之前反复打开关闭弹窗,波动的 20MB,应该是还没来得及垃圾回收。

同时,结合 process.memoryusage() 中的 rssheapToalheapUsed等参数可以发现,反复打开关闭窗口,这些参数波动不大,因此是不是可以认为没有内存泄漏?

总结

综上所述,相比于new BrowserWindow创建窗口的方式,window open具有进程单一、通信简单、数据共享方便、内存开销小、代码可维护性强等优势

通过 new BrowserWindow 创建窗口,每个窗口都是独立的进程,如果多个窗口都使用了同一个资源,比如同一张图片或者同一个脚本,那这些窗口都会独立加载这些资源

但是通过 window open 创建窗口,这些窗口共享父窗口的 render 进程,资源都是缓存在父窗口的 render 进程中。如果多个窗口都使用了同一个资源,比如同一张图片,那么这些窗口只需要加载一次,其他窗口都可以从缓存中读取。这种给资源加载带来了极大的好处,同时也有弊端,就是子窗口关闭时,我们需要手动调用 webFrame.clearCache() 清除缓存

转载自:https://juejin.cn/post/7201856537534939191
评论
请登录