【问题标题】:setInterval causes memory-leaksetInterval 导致内存泄漏
【发布时间】:2019-03-09 20:57:21
【问题描述】:

我们的代码显然做错了什么,但我们似乎无法理解是什么。如果我们想在特定的时间间隔内获取文件,为什么我们不能使用这段代码?

<html>

<head>
    <script>
        let interval = 2

        function fetchSource(source) {
            fetch(source, { cache: 'no-cache' })
                .then(response => {
                    console.log('Memory-bloating')
                })
        }

        function test() {
            fetchSource('https://upload.wikimedia.org/wikipedia/commons/3/3d/LARGE_elevation.jpg')
        }

        setInterval(test, interval * 1000)
    </script>
</head>

<body>
    Memory-bloating in chrome
</body>

</html>

这里的问题是,每次获取时,我们都会随着图像的大小增加内存,而且似乎永远不会被垃圾收集。当网站启动大约 2-3 小时后,我们的计算机中就填满了这张图片。

我们应该如何构建这个函数来避免这种相当大的内存泄漏?

【问题讨论】:

  • 我预计泄漏是在 fetch() 函数本身,你没有包括在内。
  • “我预计泄漏是在 fetch() 函数本身中,你没有包括在内。”@Alnitak 我假设 fetch 在这个问题中是一个本机 javascript 函数。
  • @RaymondNijland 完全正确。如果您在浏览器中运行此 html 文件,则您的浏览器具有 fetch-support 和 setInterval-support。我自己,我在 MacOS 上运行 Chrome。我猜(并希望)他们实现了:developer.mozilla.org/en-US/docs/Web/API/Fetch_API
  • 顺便说一句,您不能使用浏览器插件或原生 javascript 性能 api 来更好地了解代码的哪一部分是内存泄漏吗?
  • 实际上,当使用 chromes memory-tool 时,您会发现该站点只需要几 MB,而 chromes 任务管理器告诉您需要几 GB。

标签: javascript fetch setinterval fetch-api


【解决方案1】:

试试这个代码,它会停止:

   let interval = 2
   let handle = ""
   let still_fetching = false

       function fetchSource(source) {
           fetch(source, { cache: 'no-cache' })
               .then(response => {
                   console.log('Memory-bloating')
                   clearInterval(handle)
                   still_fetching = false
                   handle = null
                   handle = setInterval(test, interval * 1000)
               })
       }

       function test() {
           if(still_fetching){
               return;
           }
           still_fetching = true;
           fetchSource('https://upload.wikimedia.org/wikipedia/commons/3/3d/LARGE_elevation.jpg')
       }

       handle = setInterval(test, interval * 1000)

【讨论】:

  • 如果可以的话,值得解释一下如何你解决了这个问题并提供代码。
  • 我不清楚这是否确实解决了问题。此处保留的状态仅用于确保没有正在进行的并发获取。它有效地将setInterval 调用转换为setTimeout 调用,因为每次下载只允许间隔计时器触发一次。它还在fetchSourcetest 之间引入了不必要的循环依赖,从而破坏了这些函数之间的关注点分离。
  • 所以,这效果更好,但它似乎仍然会减慢我的应用程序的速度。运行时,它会时不时地开始垃圾收集,但它仍然可以很快达到几 GB(达到 1 GB 需要 3-5 分钟)。只是 chrome 在垃圾收集方面不够好,或者这是实现的一些问题?现在 10 分钟和 2.5GB。
  • 如果我在内存屏幕上手动按“收集垃圾”,它会再次下降到 50 MB。
  • @Alnitak 你当然是对的。这将有一个循环依赖,如果我可以没有的话,我会非常感激。我会调查是否是 chrome 问题(可能在 Canary v71 中解决)。
【解决方案2】:

在这里发布,因为代码太长,无法评论。我认为这可能是 Chrome 的错误,但通过阻止并发下载,您或许可以确认这一点。

以下是与@McBern 的代码等效的更简洁的代码,它消除了循环依赖和维护额外状态的需要:

let interval = 2

function fetchSource(source) {
    return fetch(source, { cache: 'no-cache' })  // NB: return
          .then(response => {
               console.log('Memory-bloating')
           })
    }
}

(function test() {
   let url = 'https://upload.wikimedia.org/wikipedia/commons/3/3d/LARGE_elevation.jpg';

   fetchSource(url).then(() => setTimeout(test, interval * 1000));
})();

【讨论】:

  • 并发下载不是问题,因为我们通常每 5 分钟只下载一个 ca 13MB 的文件,如果计算机打开 15-20 天,标签已增长到 10GB 以上。我刚刚做了这个例子,让你更容易帮助我理解这个问题。
  • @user2687506 很公平 - 您说其他代码“工作得更好”,尽管唯一的主要变化是他在每次下载后重置(并重新创建)间隔计时器。想知道这个版本是否也“效果更好”会很有趣。
  • Alnitak 你是对的。我可能已经对他的代码更好了。它垃圾收集了两次,然后停止了,所以可能是 Chrome 在重新加载页面之前清理了一些垃圾。不幸的是,您的代码版本并没有比他的更好。它更漂亮,如果它是一个解决方案,我会选择你的解决方案,但正如我所说,垃圾收集也不会与这段代码一起使用。不过感谢您的帮助! :)
猜你喜欢
  • 2012-12-11
  • 1970-01-01
  • 1970-01-01
  • 2015-07-06
  • 2014-06-07
  • 2013-11-20
  • 2011-10-28
  • 2016-01-18
  • 2012-12-13
相关资源
最近更新 更多