【问题标题】:AngularJS memory leakAngularJS 内存泄漏
【发布时间】:2016-02-24 08:11:54
【问题描述】:

在应用程序的不同部分之间切换时,我们的 AngularJS 应用程序出现内存泄漏问题。我们一直在努力寻找根本原因。

我们的应用中有一个主控制器。该主控制器包含另一个子控制器。这个子控制器有两个部分。在任何时候,都会显示两个部分之一。第一部分有一些带有按钮的 html。单击此按钮时,它将切换到第二部分。我们使用 jquery 在 2 个不同的部分之间切换(我知道我们不应该混合使用 jquery 和 angularjs,但这很长:-()

现在第二部分有 ui-view。在这个 ui 视图中加载了另一个带有控制器的视图。此视图包含一个按钮(用于切换到第一部分)和另一个子 ui-view。子 ui-view 加载了控制器。它使用 ng-repeat 在 UI 中填充数组。

当我们从第一部分切换到第二部分时,我们调用 $state.go 来重新加载第二部分中的父子 ui-view。

每次使用 $state.go 加载第二部分时都会发生内存泄漏。

以下是与应用代码类似的示例代码。实际的应用程序要复杂得多。但是内存泄漏的情况可以用下面的代码来模拟。

这里是Plunker Link

<body ng-app="testApp">
<div ng-controller="MainCtrl">
  <div ng-controller="SubCtrl">
    <div id="Section1">
      <h1> {{title}}</h1>
      <button ng-click="SwitchToSection2()">Switch to Section 2</button>
    </div>
    <div id="Section2" style="display:none;">
      <div ui-view="section2view"></div>
    </div>
  </div>
</div>

 var app = angular.module('testApp', ['ui.router'])

    app.config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) {

        $stateProvider
            .state("section2", {
                views: {
                    'section2view': { templateUrl: 'section2-template.html' }
                    }
            })
            .state("section2.child", {
                 templateUrl: 'section2child-template.html',
                params: { p1: "test" }
            })

    }]);

    app.controller('MainCtrl', function($scope){


    });

    app.controller('SubCtrl', function($scope, $state){
          $scope.title = "This is section 1";
          $scope.SwitchToSection2 = function(){

            $("#Section2").show();
            $("#Section1").hide();
            $state.go('section2.child', { p1: "test"}, { location: false, reload: true });
          }

    });

    app.controller('Section2Controller', function($scope, $state){

          $scope.SwitchToSection1 = function(){

            $("#Section1").show();
            $("#Section2").hide();

          }

    });

    app.controller('Section2ChildCtrl', function($scope, $state){
          $scope.itemArray = [];

          for (var i = 0; i < 1000;i++) {
              $scope.itemArray.push({
                  prop1: "Property 1",
                  prop2: "Property 2",
                  prop3: "Property 3",
              });
          }

    });

内存泄漏

我正在使用 Chrome 开发人员时间线工具来识别泄漏。下面的屏幕截图是在 2 个部分之间切换大约 10 次后拍摄的。每次我转到第 2 节时,内存大小都会增加。内存不是GC。您还可以看到节点数逐渐增加。我还使用3 snapshot profile technique 来查找分离的节点以及保留它的内容。但它没有提供任何有用的信息,除了 angularjs 引用了 dom。

如果有人能指出正确的方向,那将是很大的帮助。对不起,很长的帖子。谢谢。

【问题讨论】:

    标签: javascript jquery angularjs memory-leaks angular-ui-router


    【解决方案1】:

    我无法使用 plunker 和您提供的说明重现泄漏。我已经尝试了几次,我看不到任何泄漏发生。

    我已经改成这样了:http://plnkr.co/edit/TtrbV9929H2ymaboFTNJ?p=preview 就是这个代码:

    var app = angular.module('testApp', ['ui.router']);
    
        app.config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) {
          $stateProvider
              .state("section2", {
                views: {
                  'section2view': { templateUrl: 'section2-template.html' }
                }
              })
              .state("section2.child", {
                templateUrl: 'section2child-template.html',
                params: { p1: "test" }
              })
        }]);
    
        app.controller('MainCtrl', function($scope){
          console.log('Main controller instantiated');
          $scope.$on('$destroy', function() {
            console.log('Main controller has been destroyed');
          });
        });
    
        app.controller('SubCtrl', function($scope, $state){
          console.log('Sub controller instantiated');
          $scope.$on('$destroy', function() {
            console.log('Sub controller has been destroyed');
          });
          $scope.title = "This is section 1";
          $scope.SwitchToSection2 = function(){
            $("#Section2").show();
            $("#Section1").hide();
            $state.go('section2.child', { p1: "test"}, { location: false, reload: true });
          }
        });
    
        app.controller('Section2Controller', function($scope, $state){
          console.log('Section2 controller instantiated');
          $scope.$on('$destroy', function() {
            console.log('Section2 controller has been destroyed');
          });
          $scope.SwitchToSection1 = function(){
            $("#Section1").show();
            $("#Section2").hide();
          }
        });
    
        app.controller('Section2ChildCtrl', function($scope, $state){
          $scope.itemArray = [];
          console.log($scope.itemArray.length);
    
          for (var i = 0; i < 1000; i++) {
            $scope.itemArray.push({
              prop1: "Property 1",
              prop2: "Property 2",
              prop3: "Property 3"
            });
          }
    
          console.log('Section2 Child Ctrl controller instantiated');
          $scope.$on('$destroy', function() {
            console.log('Section2 Child Ctrl controller has been destroyed');
          });
        });
    

    因此,如果您切换,您可以看到控制器正在正确创建和销毁。迟到了,但正确。

    我使用记录的分配来查看内存使用量是否随时间增加。我像疯子一样重复状态之间的变化,并且内存使用量不会随着时间的推移而增加:

    现在如果我们看到时间线:

    你可以看到同样的东西。垃圾收集最终会启动,可能有一些开销导致创建和销毁这么多节点,但浏览器会自行恢复。

    现在,如果您的应用程序中的情况不同,我只能认为是因为 Section2 控制器和子控制器不会在切换到 Section 1 时死亡,并且因为您可能会将这些控制器中的内容存储到范围链中,这可能泄漏的东西。但这只是我没有看到代码只能想象的事情。

    值得一提的是,我已经在 OSX 10.11.1 上的最新 Chrome (46.0.2490.86) 中对此进行了测试,结果可能在其他浏览器和/或操作系统上有所不同,但如果是这种情况,罪魁祸首可能是浏览器本身。除了将 jQuery 和 Angular 混合在一起(这只是丑陋但本身并不坏)之外,我看不到任何可能导致所提供代码发生泄漏的东西。

    【讨论】:

    • 很奇怪,因为我可以在 PC 上重现。所以我在我的电脑上卸载并重新安装了 Chrome。现在我可以看到与您类似的时间线,GC 启动并清除内存。正如您现在所说,我必须研究范围链中可能阻止 GC 的其他内容。单独的问题,当切换回第 1 部分时,如何清除第 2 部分的状态而不将其保存在内存中?谢谢你
    • 因此,如果您确实删除了 jQuery,那么这个问题可能会消失,因为控制器将被销毁。如果你不能,我的建议是使用$state.go 并进入一个没有控制器的“空”状态,这样你就可以清理东西了。
    • 如何在 angularjs 中第二次切换时使状态为空?
    • 那是另一个问题 ;) 但基本上在 SwitchToSection1 上使用 $state.go 到您使用空 html 模板创建的状态。
    【解决方案2】:

    您说您使用 jQuery 是一个很长的故事,但您没有详细说明……在我看来,在您给出的场景中根本不需要使用它。我建议更新您的代码以使用角度功能并查看您是否有同样的问题。其要点如下,或根据您的代码查看this updated Plunker

    在 MainCtrl 中放置两种状态和一种控制它们的方法:

        app.controller('MainCtrl', function($scope){
          $scope.sectionOneShown = true;
          $scope.sectionTwoShown = false;
          $scope.changeSection = function() {
            console.log('changing');
            $scope.sectionOneShown = !$scope.sectionOneShown;
            $scope.sectionTwoShown = !$scope.sectionTwoShown;
          };
        });
    

    相应地更新您的 HTML 模板

    <div ng-controller="MainCtrl">
    
      <div ng-controller="SubCtrl">
    
        <div id="Section1" ng-show="sectionOneShown">
          <h1> {{title}}</h1>
          <button ng-click="switchView()">Switch to Section 2</button>
        </div>
    
        <div id="Section2" ng-show="sectionTwoShown">
          <div ui-view="section2view"></div>
        </div>
    
      </div>
    
    </div>
    

    从子控制器调用相同的方法来交换视图

    <div>
        <div style="background-color:grey;padding:10px;" >
            <h1>This is section 2</h1>
            <button ng-click="switchView()" style="margin-left:200px;">Switch To Section 1</button>
            <div ui-view></div>
        </div>     
    </div>
    

    【讨论】:

    • 投反对票。很抱歉,但这并没有试图为用户研究或回答真正的问题。还要记住,用户试图将场景精简到最基本的程度,而我们不知道应用程序的完整代码。
    • 我知道他精简了它,即使如此 OP 在他的代码中有很多似乎没有必要的内容。根据他的代码,这似乎不是一个不合理的建议,应该可以适应更大的项目。
    • 再一次,你不知道他为什么这样做,尽管这不是用户要求的。 OP询问泄漏在哪里以及他/她如何摆脱它。这很难看,OP 承认这一点,但这不应该导致任何泄漏。
    • @Ben,是的,代码中还有很多其他事情发生。放在这里的代码太多了。由于我能够通过精简版重现泄漏,我只分享了这个。代码需要太多重构才能删除 jquery。如果我们无法找到根本原因,那将是最后一种方法。我也要试试你分享的代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-01-12
    • 1970-01-01
    • 2016-05-31
    • 1970-01-01
    • 1970-01-01
    • 2016-01-05
    • 2018-02-23
    相关资源
    最近更新 更多