Подтвердить что ты не робот

Как поймать утечки памяти в приложении Angular?

У меня есть webapp, написанный в AngularJS, который в основном опроса API для двух конечных точек. Итак, каждую минуту он опросает, есть ли что-то новое.

Я обнаружил, что у него небольшая утечка памяти, и я сделал все возможное, чтобы найти его, но я не могу этого сделать. В этом процессе мне удалось уменьшить использование памяти в моем приложении, и это здорово.

Не делая ничего другого, каждый опрос вы можете увидеть всплеск в использовании памяти (что нормальный), а затем он должен упасть, но он всегда увеличивается. Я изменил очистку массивов от [] до array.length = 0, и я думаю, что я уверен, что ссылки не сохраняются, поэтому не следует сохранять это.

Я также пробовал это: https://github.com/angular/angular.js/issues/1522

Но без везения...

Итак, это сравнение между двумя кучами:

Memory heap

Большая часть утечки, по-видимому, поступает из (массива), которые, если я открываю, являются массивами, возвращаемыми анализом вызова API, но я уверен, что они не хранятся:

Это в основном структура:

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);
  });
}

В принципе, скажем, у меня есть служба sellings, поэтому это будет значение переменной service.

Затем в главном представлении:

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

И представление имеет ngRepeat, который отображает это по мере необходимости. Я потратил много времени, пытаясь понять это сам, и я не мог. Я знаю, что это сложная проблема, но есть ли у кого-нибудь идеи о том, как отслеживать это?

Редактировать 1 - Добавление демонстрации Promise:

Это makeRequest, который является функцией, используемой двумя службами:

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');
    }
});

Если я прокомментирую часть $scope.$on('api.sellings'), утечка все еще существует, но падает до 1%.

PS: Я использую последнюю версию Angular на сегодняшний день

Изменить 2 - Открыть (массив) дерево в изображении

enter image description here

Это все так, что это совершенно бесполезно imho: (

Кроме того, здесь есть 4 отчета о кучах, чтобы вы могли играть сами:

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

Изменить 3 - В ответ на @zeroflagL

Редактирование директивы не оказало никакого влияния на утечку, хотя закрывающая часть кажется лучше, поскольку она не показывает вещи кеша jQuery?

No more leakage?

Теперь директива выглядит так:

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

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

Для меня, похоже, что происходит в части (array). Существует также 3 новых кучи между опросами.

4b9b3361

Ответ 1

И ответ - это кеш.

Heap Snapshot analysis

Я не знаю, что это такое, но эта вещь растет. Это похоже на jQuery. Возможно, это кеш элементов jQuery. Вы случайно применяете плагин jQuery для одного или нескольких элементов после каждого вызова службы?

Update

Проблема состоит в том, что HTML-элементы добавляются, обрабатываются с помощью jQuery (например, через плагин popbox), но либо никогда не удаляются вообще, либо не удаляются с помощью jQuery. Обработать в этом случае означает такие вещи, как добавление обработчиков событий. Записи в кеш-объекте (независимо от того, для чего он) удаляются, только если jQuery знает, что элементы были удалены. Это элементы должны быть удалены с помощью jQuery.

Обновление 2

Не совсем понятно, почему эти записи в кеше не были удалены, поскольку angular должен использовать jQuery, когда он включен. Но они добавлены через плагин, упомянутый в комментариях, и содержат обработчики событий и данные. AFAIK Antonio изменил код плагина, чтобы развязать обработчики событий и удалить данные в модуле destroy(). Это в конечном итоге устранило утечку памяти.

Ответ 2

Стандартный способ браузера исправлять утечки памяти - это обновить страницу. И сборка мусора JavaScript вроде бы ленива, и, скорее всего, это банковское дело. И поскольку Angular, как правило, SPA, браузер никогда не получает возможности обновиться.

Но у нас есть одно преимущество: Javascript - это прежде всего иерархический язык сверху вниз. Вместо поиска утечек памяти снизу вверх, мы можем очистить их сверху вниз.

Поэтому я придумал это решение, которое работает, но может или не может быть на 100% эффективным в зависимости от вашего приложения.

Главная страница

Типичная домашняя страница приложения Angular состоит из некоторого контроллера и ng-view. Вот так:

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

Контроллер

Затем, чтобы "обновить" приложение в контроллере, которое было бы MainController из вышеприведенного кода, мы избыточно вызываем jQuery.empty() и Angular.empty(), чтобы удостовериться, что любые ссылки между библиотеками очищено.

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

и вызвать выше, прежде чем начнется маршрутизация, имитируя обновление страницы:

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

Результат

Это своего рода хакерский метод для "обновления поведения браузера", очистки DOM и, надеюсь, любых утечек. Надеюсь, что это поможет.