+1 GB的内存分配20小时后,Angular Firebase应用程序崩溃


13

我发现使用 AngularFireAuthModule from '@angular/fire/auth';会导致内存泄漏,导致20小时后浏览器崩溃。

版:

我对所有软件包都使用ncu -u进行了更新的最新版本。

角火: "@angular/fire": "^5.2.3",

Firebase版本: "firebase": "^7.5.0"

繁殖方法:

我在StackBliztz编辑器上制作了最少的可复制代码

这是直接测试错误的链接StackBlizt测试

症状:

您可以检查自己代码是否无效。它只是打印你好世界。但是,Angular App使用的JavaScript内存增加了11 kb / s(Chrome Task Manager CRTL + ESC)。在打开浏览器10个小时后,使用的内存达到约800 mb(内存占用量约为1.6 Gb的两倍!)

结果,浏览器的内存不足,chrome标签页崩溃了。

在使用性能选项卡下的chrome内存配置文件进行进一步调查之后,我清楚地注意到侦听器的数量每秒增加2个,因此JS堆也相应增加。

在此处输入图片说明

导致内存泄漏的代码:

我发现使用该AngularFireAuthModule 模块会导致内存泄漏,无论它是注入到component构造函数中还是注入到service

import { Component } from '@angular/core';
import {AngularFireAuth} from '@angular/fire/auth';
import {AngularFirestore} from '@angular/fire/firestore';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'memoryleak';
  constructor(public auth: AngularFireAuth){

  }
}

问题

这可能是FirebaseAuth实施中的错误,我已经打开了Github问题,但是我正在寻找解决此问题的方法。我迫切需要一个解决方案。我不介意即使跨选项卡的会话未同步。我不需要那个功能。我在某处读过

如果您不需要此功能,则Firebase V6模块化​​工作将使您能够切换到localStorage,该存储具有用于检测交叉表的更改的存储事件,并可能为您提供定义自己的存储接口的能力。

如果那是唯一的解决方案,那么如何实现呢?

我只需要任何能够阻止这种不必要的监听器增加的解决方案,因为它会减慢计算机速度并使我的应用程序崩溃。我的应用程序需要运行20多个小时,因此由于此问题,现在无法使用。我迫切需要一个解决方案。



我未能在您的示例中重现您的问题
Sergey Mell,

@SergeyMell您是否使用了我在StackBlitz上发布的代码?
TSR

是。实际上,我在谈论它。
谢尔盖·梅尔

尝试下载代码并在本地运行。我也将其上传到驱动器中,以防万一drive.google.com/file/d/1fvo8eJrbYpZWfSXM5h_bw5jh5tuoWAB2/…–
TSR

Answers:


7

TLDR:增加侦听器数量是预期的行为,并将在垃圾回收时重置。Firebase v7.5.0中已修复了导致Firebase Auth中内存泄漏的错误,请参阅#1121,检查package-lock.json以确认您使用的版本正确。如果不确定,请重新安装该firebase软件包。

Firebase的早期版本是通过Promise链轮询IndexedDB的,这会导致内存泄漏,请参阅JavaScript的Promise Leaks Memory

var repeat = function() {
  self.poll_ =
      goog.Timer.promise(fireauth.storage.IndexedDB.POLLING_DELAY_)
      .then(goog.bind(self.sync_, self))
      .then(function(keys) {
        // If keys modified, call listeners.
        if (keys.length > 0) {
          goog.array.forEach(
              self.storageListeners_,
              function(listener) {
                listener(keys);
              });
        }
      })
      .then(repeat)
      .thenCatch(function(error) {
        // Do not repeat if cancelled externally.
        if (error.message != fireauth.storage.IndexedDB.STOP_ERROR_) {
          repeat();
        }
      });
  return self.poll_;
};
repeat();

在后续版本中使用非递归函数调用进行了修复:

var repeat = function() {
  self.pollTimerId_ = setTimeout(
      function() {
        self.poll_ = self.sync_()
            .then(function(keys) {
              // If keys modified, call listeners.
              if (keys.length > 0) {
                goog.array.forEach(
                    self.storageListeners_,
                    function(listener) {
                      listener(keys);
                    });
              }
            })
            .then(function() {
              repeat();
            })
            .thenCatch(function(error) {
              if (error.message != fireauth.storage.IndexedDB.STOP_ERROR_) {
                repeat();
              }
            });
      },
      fireauth.storage.IndexedDB.POLLING_DELAY_);
};
repeat();


关于线性增加的侦听器数目:

预期线性增加的侦听器数量,因为这是Firebase用来轮询IndexedDB的方法。但是,只要GC需要,侦听器将被删除。

读取问题576302:错误显示内存(监听器xhr和负载)泄漏

V8定期执行次要GC,这会导致堆大小的微小下降。您实际上可以在火焰图上看到它们。但是,次要GC可能不会收集所有垃圾,这显然对于侦听器会发生。

工具栏按钮将调用能够收集侦听器的Major GC。

DevTools尝试不干扰正在运行的应用程序,因此它不会自行强制执行GC。


为了确认分离的侦听器是否已被垃圾回收,我添加了以下代码片段以对JS堆施加压力,从而迫使GC触发:

var x = ''
setInterval(function () {
  for (var i = 0; i < 10000; i++) {
    x += 'x'
  }
}, 1000)

听众被垃圾收集

如您所见,触发GC时,会定期删除分离的侦听器。



关于侦听器数量和内存泄漏的类似stackoverflow问题和GitHub问题:

  1. Chrome开发人员工具性能分析结果中的侦听器
  2. JavaScript侦听器不断增加
  3. 简单的应用导致内存泄漏?
  4. $ http'GET'内存泄漏(NO!)-侦听器数量(AngularJS v.1.4.7 / 8)

我确认使用7.5.0,并在不同的环境上测试了多次。即使this.auth.auth.setPersistence('none')也无法防止内存泄漏。请使用此处的代码自行
TSR

您的测试步骤是什么?我需要过夜放置才能看到浏览器崩溃吗?在我的情况下,侦听器编号总是在GC启动后重置,并且内存总是回到160mb。
约书亚·陈

@TSR通话this.auth.auth.setPersistence('none')ngOnInit,而不是构造函数来禁用持久性。
约书亚·陈

@JoshuaChan何时调用服务的方法有关系吗?它被注入到构造函数中,并在其主体中可用。为什么要进去ngOnInit
谢尔盖

@Sergey主要用于最佳实践。但是对于这种特定情况,我对两种调用方式都进行了CPU性能分析setPersistence,发现如果在构造函数中完成,则仍会对IndexedDB进行函数调用,而如果在构造中完成ngOnInit,则不会对IndexedDB进行调用,并非完全如此确定为什么会这样
Joshua Chan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.