39 votes

Comment attraper les fuites de mémoire dans une application angulaire?

J'ai une application web écrite en AngularJS qui, en gros, des sondages d'une API à deux points de terminaison. De la sorte, chaque minute, il les sondages pour voir si il n'y a rien de nouveau.

J'ai découvert qu'il a une petite fuite de mémoire et j'ai fait de mon mieux pour le trouver, mais je ne suis pas en mesure de le faire. Dans le processus que j'ai réussi à réduire l'utilisation de la mémoire de mon application, ce qui est excellent.

Sans rien faire d'autre, tous les sondages, vous pouvez voir une pointe dans l'utilisation de la mémoire (c'est normal) et ensuite, il devrait tomber, mais il est toujours en augmentation. J'ai changé le nettoyage des tableaux à partir de [] de array.length = 0 et je pense que je suis sûr que les références ne pas persister, ce qui ne devrait pas être retenir de tout cela.

J'ai aussi essayé ceci: https://github.com/angular/angular.js/issues/1522

Mais sans un peu de chance...

Donc, c'est une comparaison entre deux tas:

Memory heap

La plupart de la fuite semble venir de (array) qui, si je l'ouvre, sont les tableaux retournés par l'analyse de l'appel d'API, mais je suis sûr qu'ils ne sont pas stockés:

C'est fondamentalement la structure:

poll: function(service) {
  var self = this;
  log('Polling for %s', service);

  this[service].get().then(function(response) {
    if (!response) {
      return;
    }

    var interval = response.headers ? (parseInt(response.headers('X-Poll-Interval'), 10) || 60) : 60;

    services[service].timeout = setTimeout(function(){
      $rootScope.$apply(function(){
        self.poll(service);
      });
    }, interval * 1000);

    services[service].lastRead = new Date();
    $rootScope.$broadcast('api.'+service, response.data);
  });
}

En gros, disons que j'ai un sellings service donc, ce serait la valeur de l' service variable.

Puis, dans l'écran principal:

$scope.$on('api.sellings', function(event, data) {
  $scope.sellings.length = 0;
  $scope.sellings = data;
});

Et la vue ne doit avoir un ngRepeat qui le rend nécessaire. J'ai passé beaucoup de temps à essayer de comprendre par moi-même et je ne pouvais pas. Je sais que c'est un dur problème mais est-ce que quelqu'un a une idée sur la façon d'obtenir cela?

Edit 1 - Ajout de la Promesse de la vitrine:

C'est - makeRequest ce qui est la fonction utilisée par les deux services:

return $http(options).then(function(response) {
    if (response.data.message) {
      log('api.error', response.data);
    }

    if (response.data.message == 'Server Error') {    
      return $q.reject();
    }

    if (response.data.message == 'Bad credentials' || response.data.message == 'Maximum number of login attempts exceeded') {
      $rootScope.$broadcast('api.unauthorized');
      return $q.reject();
    }

    return response;
    }, function(response) {
    if (response.status == 401 || response.status == 403) {
      $rootScope.$broadcast('api.unauthorized');
    }
});

Si j'en commentaire l' $scope.$on('api.sellings') partie, la fuite existe encore, mais il tombe à 1%.

PS: je suis en utilisant les dernières Angulaire de la version à jour

Edit 2 - Ouverture (array) de l'arbre dans une image

enter image description here

C'est comme tout ce qui c'est donc assez inutile à mon humble avis :(

Aussi, voici 4 tas de rapports de sorte que vous pouvez jouer vous-même:

https://www.dropbox.com/s/ys3fxyewgdanw5c/Heap.zip

Edit 3 - En réponse à @zeroflagL

Modification de la directive, n'ont pas d'impact sur la fuite bien que la fermeture de la partie, qui semble mieux depuis qu'il ne l'est pas jQuery cache des choses?

No more leakage?

La directive ressemble maintenant à ceci

var destroy = function(){
  if (cls){
    stopObserving();
    cls.destroy();
    cls = null;
  }
};

el.on('$destroy', destroy);
scope.$on('$destroy', destroy);

Pour moi, il me semble que ce qui se passe sur l' (array) partie. Il y a aussi 3 nouveaux segments entre pollings.

28voto

zeroflagL Points 7379

Et la réponse est cache.

Heap Snapshot analysis

Je ne sais pas ce que c'est, mais cette chose qui pousse. Il semble être lié à jQuery. C'est peut-être le jQuery élément de cache. Avez-vous, par hasard, appliquer un plugin jQuery sur un ou plusieurs des éléments après chaque appel de service?

Mise à jour

Le problème est que les éléments HTML sont ajoutés, traitées avec jQuery (par exemple via la popbox plugin), mais jamais retiré ou n'est pas retiré avec jQuery. À traiter dans ce cas, signifie des choses comme ajouter des gestionnaires d'événements. Les entrées dans le cache de l'objet (quel qu'il soit) de faire seulement obtenir supprimée si jQuery sait que les éléments ont été supprimés. Qui est les éléments qui doivent être enlevés avec jQuery.

Mise à jour 2

Ce n'est pas tout à fait clair pourquoi ces entrées dans le cache n'ont pas été supprimées, comme angulaire est censé utiliser jQuery, lorsqu'il est inclus. Mais ils ont été ajoutés à l'aide du plugin mentionné dans les commentaires et contenus des gestionnaires d'événements et de données. Autant que je sache, Antonio a modifié le code du plugin pour supprimer la liaison avec les gestionnaires d'événements et de supprimer les données dans le plugin destroy() méthode. Que finalement retiré de la fuite de mémoire.

3voto

jmb.mage Points 127

Le standard du navigateur de façon à corriger les fuites de mémoire est d'actualiser la page. JavaScript et la collecte des ordures est une sorte de paresseux, probablement de miser sur cette. Et depuis Angulaire est généralement un SPA, le navigateur n'est jamais une occasion de se rafraîchir.

Mais nous avons 1 chose à notre avantage: Javascript est principalement un top-down hierarchial langue. Au lieu de rechercher les fuites de mémoire, de bas en haut, nous pouvons être en mesure de les nettoyer de haut en bas.

Donc je suis venu avec cette solution, qui fonctionne, mais peut ou peut ne pas être efficace à 100% en fonction de votre application.

La Page D'Accueil

Le typique Angulaire de la page d'accueil de l'application se compose d'un Contrôleur et d' ng-view. Comme ceci:

<div ng-controller="MainController as vm"> <div id="main-content-app" ng-view></div> </div>

Le Contrôleur

Ensuite pour "rafraîchir" l'application dans le contrôleur, ce qui serait MainController du code ci-dessus, nous assurer la redondance de l'appel de jQuery .empty() et Angulaire de l' .empty() juste pour s'assurer que toutes les références de bibliothèque sont effacées.

function refreshApp() {
var host = document.getElementById('main-content-app');
if(host) {
    var mainDiv = $("#main-content-app");
    mainDiv.empty();
    angular.element(host).empty();
}
}

et d'appeler le ci-dessus avant de routage commence, en simulant une actualisation de la page:

$rootScope.$on('$routeChangeStart',
function (event, next, current) {
    refreshApp();
}
);

Résultat

C'est une sorte de hacky méthode pour "rafraichir le navigateur type de comportement", la compensation de la DOM et nous espérons que toutes les fuites. Espérons que cela aide.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X