Обнаружение утечек памяти в JavaScript с помощью Chrome

163

Я создал очень простой контрольный пример, который создает представление Backbone, присоединяет обработчик к событию и создает экземпляр пользовательского класса. Я полагаю, что при нажатии кнопки «Удалить» в этом примере все будет очищено и не должно быть утечек памяти.

Jsfiddle для кода находится здесь: http://jsfiddle.net/4QhR2/

// scope everything to a function
function main() {

    function MyWrapper() {
        this.element = null;
    }
    MyWrapper.prototype.set = function(elem) {
        this.element = elem;
    }
    MyWrapper.prototype.get = function() {
        return this.element;
    }

    var MyView = Backbone.View.extend({
        tagName : "div",
        id : "view",
        events : {
            "click #button" : "onButton",
        },    
        initialize : function(options) {        
            // done for demo purposes only, should be using templates
            this.html_text = "<input type='text' id='textbox' /><button id='button'>Remove</button>";        
            this.listenTo(this,"all",function(){console.log("Event: "+arguments[0]);});
        },
        render : function() {        
            this.$el.html(this.html_text);

            this.wrapper = new MyWrapper();
            this.wrapper.set(this.$("#textbox"));
            this.wrapper.get().val("placeholder");

            return this;
        },
        onButton : function() {
            // assume this gets .remove() called on subviews (if they existed)
            this.trigger("cleanup");
            this.remove();
        }
    });

    var view = new MyView();
    $("#content").append(view.render().el);
}

main();

Однако мне неясно, как использовать профилировщик Google Chrome, чтобы убедиться, что это действительно так. На снимке профилировщика кучи есть несколько миллиардов вещей, и я понятия не имею, как декодировать, что хорошо / плохо. Обучающие программы, которые я видел до сих пор, либо просто говорят мне «использовать профилировщик снимков», либо дают мне очень подробный манифест о том, как работает весь профилировщик. Можно ли просто использовать профилировщик в качестве инструмента или мне действительно нужно понять, как все это было спроектировано?

РЕДАКТИРОВАТЬ: Учебники, подобные этим:

Устранение утечки памяти в Gmail

Использование DevTools

Представляют некоторые из более сильных материалов, из того, что я видел. Однако, помимо представления концепции 3 Snapshot Technique , я считаю, что они предлагают очень мало с точки зрения практических знаний (для начинающего, как я). Учебное пособие «Использование DevTools» не работает на реальном примере, поэтому его расплывчатое и общее концептуальное описание вещей не слишком полезно. Что касается примера «Gmail»:

Итак, вы нашли утечку. Что теперь?

  • Изучите пути утечки предметов в нижней половине панели «Профили».

  • Если сайт размещения не может быть легко выведен (например, слушатели событий):

  • Инструментарий конструктора сохраняющего объекта через консоль JS для сохранения трассировки стека для выделений

  • Используя Закрытие? Включите соответствующий существующий флаг (например, goog.events.Listener.ENABLE_MONITORING), чтобы установить свойство creationStack во время построения.

После прочтения я чувствую себя более растерянным, а не менее. И, опять же, это просто говорит мне делать вещи, а не как это делать. С моей точки зрения, вся информация либо слишком расплывчата, либо будет иметь смысл только для тех, кто уже понял этот процесс.

Некоторые из этих более конкретных вопросов были подняты в ответе @Jonathan Naguin ниже.

EleventyOne
источник
2
Я ничего не знаю о тестировании использования памяти в браузерах, но если вы этого не видели, статья Адди Османи о веб-инспекторе Chrome может оказаться полезной.
Пол Д. Уэйт
1
Спасибо за предложение, Пол. Однако, когда я делаю один снимок перед нажатием кнопки «Удалить», а затем еще один после того, как он щелкнул, и затем выбираю «объекты, распределенные между снимками 1 и 2» (как предложено в его статье), все еще присутствует более 2000 объектов. Например, есть 4 записи HTMLButtonElement, что для меня не имеет смысла. По правде говоря, я понятия не имею, что происходит.
EleventyOne
3
До, это не звучит особенно полезно. Может случиться так, что с таким языком сборки мусора, как JavaScript, вы на самом деле не должны проверять, что вы делаете с памятью на таком гранулярном уровне, как ваш тест. Лучший способ проверить наличие утечек памяти - это позвонить main10000 раз, а не один раз, и посмотреть, не окажется ли у вас больше памяти в конце.
Пол Д. Уэйт
3
@ PaulD.Waite Да, возможно. Но мне кажется, что мне все еще нужен детальный анализ уровня, чтобы точно определить, в чем проблема, а не просто сказать (или не сказать): «Хорошо, где-то здесь есть проблема с памятью». И у меня складывается впечатление, что я должен иметь возможность использовать их профилировщик на таком гранулярном уровне ... Я просто не уверен, как :(
EleventyOne
Вы должны взглянуть на youtube.com/watch?v=L3ugr9BJqIs
май

Ответы:

205

Хороший рабочий процесс для обнаружения утечек памяти - это метод трех снимков , впервые использованный Лориной Ли и командой Gmail для решения некоторых проблем с памятью. Шаги, в общем:

  • Сделайте снимок кучи.
  • Делай вещи.
  • Сделайте еще один снимок кучи.
  • Повторите то же самое.
  • Сделайте еще один снимок кучи.
  • Фильтруйте объекты, расположенные между снимками 1 и 2 в представлении «Сводка» снимка 3.

Для вашего примера я адаптировал код, чтобы показать этот процесс (вы можете найти его здесь ), откладывающий создание Backbone View до события click кнопки Start. Сейчас:

  • Запустите HTML (сохраненный локально с использованием этого адреса ) и сделайте снимок.
  • Нажмите Пуск, чтобы создать вид.
  • Сделайте еще один снимок.
  • Нажмите удалить.
  • Сделайте еще один снимок.
  • Фильтруйте объекты, расположенные между снимками 1 и 2 в представлении «Сводка» снимка 3.

Теперь вы готовы обнаружить утечки памяти!

Вы заметите узлы нескольких разных цветов. Красные узлы не имеют прямых ссылок из Javascript на них, но живы, потому что они являются частью отдельного дерева DOM. Может быть узел в дереве, на который ссылается Javascript (может быть, как замыкание или переменная), но по совпадению предотвращает сборку мусора всего дерева DOM.

введите описание изображения здесь

Желтые узлы, однако, имеют прямые ссылки из Javascript. Ищите желтые узлы в том же отдельном дереве DOM, чтобы найти ссылки из вашего Javascript. Должна быть цепочка свойств, ведущая от окна DOM к элементу.

В частности, вы можете увидеть элемент HTML Div, помеченный как красный. Если вы развернете элемент, то увидите, что на него ссылается функция «кеш».

введите описание изображения здесь

Выберите строку и в консоли введите $ 0, вы увидите фактическую функцию и местоположение:

>$0
function cache( key, value ) {
        // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
        if ( keys.push( key += " " ) > Expr.cacheLength ) {
            // Only keep the most recent entries
            delete cache[ keys.shift() ];
        }
        return (cache[ key ] = value);
    }                                                     jquery-2.0.2.js:1166

Здесь ссылка на ваш элемент. К сожалению, вы мало что можете сделать, это внутренний механизм от jQuery. Но просто для целей тестирования перейдите к функции и измените метод на:

function cache( key, value ) {
    return value;
}

Теперь, если вы повторите процесс, вы не увидите ни одного красного узла :)

Документация:

Джонатан Нагуин
источник
8
Я ценю ваши усилия. Действительно, техника трех снимков регулярно упоминается в уроках. К сожалению, детали часто не учитываются. Например, я ценю введение $0в консоль функции, которая была для меня новой - конечно, я понятия не имею, что она делает или как вы знали, как ее использовать ( $1кажется бесполезным, хотя, $2кажется, делает то же самое). Во-вторых, как вы узнали, чтобы выделить строку, #button in function cache()а не другие десятки строк? Наконец, есть красные узлы NodeListи HTMLInputElementтоже, но я не могу понять их.
EleventyOne
7
Как вы узнали, что cacheстрока содержит информацию, а остальные нет? Есть множество ветвей, которые имеют меньшее расстояние, чем это cache. И я не уверен, откуда ты знал, что HTMLInputElementэто дитя HTMLDivElement. Я вижу, что на него ссылаются внутри («родной в HTMLDivElement»), но он также ссылается на себя и два HTMLButtonElements, что не имеет смысла для меня. Я, конечно, ценю, что вы определили ответ для этого примера, но я действительно не знаю, как обобщить это на другие вопросы.
EleventyOne
2
Странно, я использовал ваш пример и получил другой результат, чем вы (из вашего скриншота). Тем не менее, я очень ценю вашу помощь. Я думаю, что у меня достаточно на данный момент, и когда у меня есть реальный пример, с которым мне нужна конкретная помощь, я создам новый вопрос здесь. Еще раз спасибо.
EleventyOne
2
Объяснение $ 0 можно найти здесь: developer.chrome.com/devtools/docs/commandline-api#0-4
Сукрит Гупта
4
Что Filter objects allocated between Snapshots 1 and 2 in Snapshot 3's "Summary" view.значит?
К - Токсичность в СО растет.
8

Вот совет по профилированию памяти jsfiddle: используйте следующий URL, чтобы изолировать ваш результат jsfiddle, он удаляет всю структуру jsfiddle и загружает только ваш результат.

http://jsfiddle.net/4QhR2/show/

Я так и не смог понять, как использовать Timeline и Profiler для отслеживания утечек памяти, пока не прочитал следующую документацию. После прочтения раздела, озаглавленного «Трекер размещения объектов», я смог использовать инструмент «Запись выделения кучи» и отследить некоторые отдельные узлы DOM.

Я исправил проблему, переключившись с привязки событий jQuery на делегирование событий Backbone. Насколько я понимаю, более новые версии Backbone автоматически отменят привязку событий для вас, если вы позвоните View.remove(). Выполните некоторые демонстрации самостоятельно, они настроены с утечками памяти, чтобы вы могли их идентифицировать. Не стесняйтесь задавать вопросы здесь, если вы все еще не получили его после изучения этой документации.

https://developers.google.com/chrome-developer-tools/docs/javascript-memory-profiling

Рик Саггс
источник
6

В основном вам нужно посмотреть на количество объектов в вашем снимке кучи. Если число объектов увеличивается между двумя моментальными снимками, и вы избавились от объектов, то у вас есть утечка памяти. Мой совет - искать обработчики событий в вашем коде, которые не отсоединяются.

Константин Динев
источник
3
Например, если я посмотрю на снимок кучи jsfiddle, прежде чем нажать «Удалить», будет присутствовать более 100 000 объектов. Где я буду искать объекты, которые на самом деле создал мой код jsfiddle? Я думал, что это Window/http://jsfiddle.net/4QhR2/showможет быть полезно, но это просто бесконечные функции. Я понятия не имею, что там происходит.
EleventyOne
@EleventyOne: я бы не использовал jsFiddle. Почему бы просто не создать файл на своем компьютере для тестирования?
Голубое небо
1
@BlueSkies Я сделал jsfiddle, чтобы люди могли работать с одной и той же кодовой базой. Тем не менее, когда я создаю файл на своем компьютере для тестирования, в снимке кучи все еще остается более 50 000 объектов.
EleventyOne
@EleventyOne Один снимок кучи не позволяет понять, есть ли у вас утечка памяти или нет. Вам нужно как минимум два.
Константин Динев,
2
На самом деле. Я подчеркивал, насколько сложно знать, что искать, когда присутствуют тысячи объектов.
EleventyOne
3

Вы также можете посмотреть на вкладку Timeline в инструментах разработчика. Запишите использование вашего приложения и следите за количеством DOM Node и Event listener.

Если график памяти действительно указывает на утечку памяти, то вы можете использовать профилировщик, чтобы выяснить, что происходит утечка.

Роберт Фалькен
источник
3

Вы также можете прочитать:

http://addyosmani.com/blog/taming-the-unicorn-easing-javascript-memory-profiling-in-devtools/

Он объясняет использование инструментов разработчика Chrome и дает несколько пошаговых советов о том, как подтвердить и обнаружить утечку памяти с помощью сравнения снимков кучи и различных доступных представлений снимков hep.

bennidi
источник
2

Я второй совет, чтобы сделать снимок кучи, они отлично подходят для обнаружения утечек памяти, Chrome отлично делает снимок.

В своем исследовательском проекте для моей степени я создавал интерактивное веб-приложение, которое должно было генерировать много данных, построенных в «слоях», многие из этих слоев были бы «удалены» в пользовательском интерфейсе, но по какой-то причине память не была будучи освобожденным, используя инструмент моментальных снимков, я смог определить, что JQuery сохранял ссылку на объект (источник был, когда я пытался вызвать .load()событие, которое сохраняло ссылку, несмотря на выход из области видимости). Имея под рукой эту информацию, единолично сохранивший мой проект, это очень полезный инструмент, когда вы используете библиотеки других людей, и у вас есть проблема с давними ссылками, мешающими GC выполнять свою работу.

РЕДАКТИРОВАТЬ: также полезно заранее планировать, какие действия вы собираетесь выполнять, чтобы минимизировать время, затрачиваемое на создание снимков, выдвигать гипотезы о том, что может быть причиной проблемы, и тестировать каждый сценарий, делая снимки до и после.

ProgrammerInProgress
источник
0

Несколько важных замечаний, касающихся выявления утечек памяти с помощью инструментов Chrome Developer:

1) Сам Chrome имеет утечки памяти для определенных элементов, таких как поля пароля и номера. https://bugs.chromium.org/p/chromium/issues/detail?id=967438 . Избегайте их использования при отладке, так как они создают ваш снимок кучи при поиске отдельных элементов.

2) Избегайте регистрации чего-либо на консоли браузера. Chrome не будет собирать объекты, записанные на консоль, что повлияет на ваш результат. Вы можете подавить вывод, поместив следующий код в начало скрипта / страницы:

console.log = function() {};
console.warn = console.log;
console.error = console.log;

3) Используйте снимки кучи и ищите «detach», чтобы идентифицировать отдельные элементы DOM. При наведении курсора на объекты вы получаете доступ ко всем свойствам, включая id и outerHTML, которые могут помочь идентифицировать каждый элемент. Снимок экрана JS Heap Snapshot с подробностями об отдельном элементе DOM Если отсоединенные элементы все еще слишком универсальны для распознавания, назначьте им уникальные идентификаторы с помощью консоли браузера перед запуском теста, например:

var divs = document.querySelectorAll("div");
for (var i = 0 ; i < divs.length ; i++)
{
    divs[i].id = divs[i].id || "AutoId_" + i;
}
divs = null; // Free memory

Теперь, когда вы идентифицируете отдельный элемент с помощью, скажем, id = "AutoId_49", перезагрузите страницу, снова выполните приведенный выше фрагмент и найдите элемент с id = "AutoId_49" с помощью инспектора DOM или document.querySelector (..) , Естественно, это работает, только если содержание вашей страницы предсказуемо.

Как я запускаю свои тесты для выявления утечек памяти

1) Загрузить страницу (с подавленным выводом на консоль!)

2) Делать вещи на странице, которые могут привести к утечкам памяти

3) Используйте Developer Tools, чтобы сделать снимок кучи и выполнить поиск «detach»

4) Наведите указатель мыши на элементы, чтобы идентифицировать их по свойствам id или outerHTML

Джимми Томсен
источник
Кроме того, всегда полезно отключить минификацию / углизацию, поскольку это затрудняет отладку в браузере.
Джимми Томсен