Поиск утечек памяти в JavaScript с помощью Chrome
Я создал очень простой тестовый пример, который создает представление 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, чтобы убедиться, что это, по сути, случай. Есть gazillion вещи, которые появляются на снимке профайлера кучи, и я понятия не имею, как декодировать, что хорошо/плохо. Учебники, которые я видел на нем, пока либо просто говорят мне "использовать профилировщик моментальных снимков", либо дать мне подробный манифест о том, как работает весь профайлер. Можно ли просто использовать профилировщик в качестве инструмента, или мне действительно нужно понять, как все это было спроектировано?
EDIT: Учебники, подобные этим:
Исправление утечки памяти Gmail
Использование DevTools
Являются представителем какого-то более сильного материала там, из того, что я видел. Однако, невзирая на концепцию 3 Snapshot Technique, я нахожу, что они предлагают очень мало практических знаний (для новичка, подобного мне). Учебник "Использование DevTools" не работает на реальном примере, поэтому его неопределенное и общее концептуальное описание вещей не слишком полезно. Что касается примера Gmail:
Итак, вы обнаружили утечку. Теперь что?
-
Изучите путь удержания просочившихся объектов в нижней половине панели "Профили".
-
Если сайт распределения не может быть легко выведен (например, прослушиватели событий):
-
Инструмент конструктор сохраняющего объекта через консоль JS для сохранения трассировки стека для распределений
-
Использование закрытия? Включите соответствующий существующий флаг (т.е. Goog.events.Listener.ENABLE_MONITORING), чтобы установить свойство createStack во время построения
Я больше смущен, прочитав это, не меньше. И, опять же, это просто говорит мне делать что-то, а не как их делать. С моей точки зрения, вся информация там слишком расплывчата или будет иметь смысл только для того, кто уже понял этот процесс.
Некоторые из этих более конкретных проблем были подняты в ответе @Джонатан Нагуин ниже.
Ответы
Ответ 1
Хороший рабочий процесс для обнаружения утечек памяти - это метод трех снимок, впервые используемый Лореей Ли и командой Gmail для решения некоторых проблем с памятью. Обычно этапы:
- Сделайте снимок кучи.
- Сделайте что-нибудь.
- Сделайте еще один снимок кучи.
- Повторите то же самое.
- Сделайте еще один снимок кучи.
- Фильтровать объекты, выделенные между моментальными снимками 1 и 2 в снимке 3 "Сводка".
В вашем примере я адаптировал код, чтобы показать этот процесс (вы можете найти его здесь), задерживая создание Backbone View до тех пор, пока событие щелчка кнопки "Пуск". Сейчас:
- Запустите HTML (сохраненный локально с помощью этого address) и сделайте снимок.
- Нажмите "Пуск", чтобы создать представление.
- Сделайте еще один снимок.
- Нажмите "Удалить".
- Сделайте еще один снимок.
- Фильтровать объекты, выделенные между моментальными снимками 1 и 2 в снимке 3 "Сводка".
Теперь вы готовы найти утечки памяти!
Вы увидите узлы нескольких разных цветов. У красных узлов нет прямых ссылок от Javascript к ним, но они живы, потому что они являются частью отдельного дерева DOM. Может быть node в дереве, на который ссылается Javascript (возможно, как закрытие или переменная), но по совпадению предотвращает сбор всего дерева DOM.
![enter image description here]()
Однако желтые узлы имеют прямые ссылки от Javascript. Ищите желтые узлы в том же отдельном дереве DOM, чтобы найти ссылки со своего Javascript. Должна существовать цепочка свойств, ведущая из окна DOM к элементу.
В вашем конкретном разделе вы можете увидеть элемент HTML Div, отмеченный как красный. Если вы разберете элемент, вы увидите, что на него ссылается функция "кеш".
![enter image description here]()
Выберите строку и в вашей консоли тип $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;
}
Теперь, если вы повторите этот процесс, вы не увидите красный node:)
Документация:
Ответ 2
Вот совет по профилированию памяти 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
Ответ 3
В основном вам нужно посмотреть количество объектов внутри моментального снимка кучи. Если количество объектов увеличивается между двумя моментальными снимками, и вы удаляете объекты, тогда у вас есть утечка памяти. Мой совет - искать обработчики событий в своем коде, которые не отделяются.
Ответ 4
Вы также можете прочитать:
http://addyosmani.com/blog/taming-the-unicorn-easing-javascript-memory-profiling-in-devtools/
В нем объясняется использование инструментов разработчика Chrome и даются некоторые пошаговые рекомендации о том, как подтвердить и найти утечку памяти, используя сравнение моментальных снимков кучи и различные доступные снимки снимков hep.
Ответ 5
В Google есть ознакомительное видео, которое будет очень полезно для поиска утечек памяти JavaScript.
https://www.youtube.com/watch?v=L3ugr9BJqIs
Ответ 6
Вы также можете посмотреть вкладку "Временная шкала" в инструментах разработчика. Запишите использование вашего приложения и следите за DOM Node и счетчиком прослушивания событий.
Если график памяти действительно указывает на утечку памяти, вы можете использовать профилировщик, чтобы выяснить, что происходит.
Ответ 7
Во-вторых, совет, чтобы сделать снимок кучи, они отлично подходят для обнаружения утечек памяти, хром отлично справляется с моментальным снимком.
В моем исследовательском проекте для моей степени я создавал интерактивное веб-приложение, которое должно было генерировать много данных, собранных в "слоях", многие из этих уровней были бы "удалены" в пользовательском интерфейсе, но по какой-то причине память не был освобожден, с помощью инструмента моментального снимка я смог определить, что JQuery сохранял ссылку на объект (источник был, когда я пытался запустить событие .load()
, которое сохраняло ссылку, несмотря на выход из области), Имея эту информацию в ручном режиме, я сохранил свой проект, это очень полезный инструмент, когда вы используете библиотеки других людей, и у вас есть эта проблема с затяжными ссылками, которые останавливают выполнение GC.
EDIT:
Также полезно заранее планировать, какие действия вы собираетесь выполнять, чтобы свести к минимуму время, затраченное на моментальные снимки, выдвинуть гипотезу о том, что может вызвать проблему, и протестировать каждый сценарий, сделать снимки до и после.