Плохая пресса о Node.js часто ссылается на проблемы с производительностью.
Это не означает, что у Node.js больше проблем, чем у других технологий.
Пользователю просто необходимо иметь в виду некоторые особенности его эксплуатации.
Хотя кривая обучения этой технологии плоская, механизмы, обеспечивающие ее работу, довольно сложны.
Их необходимо понимать, чтобы предотвратить возникновение ошибок в работе.
И если что-то пойдет не так, нужно знать, как быстро все привести в порядок.
В этой статье Дэниел Хан рассказывает о том, как Node.js управляет памятью и как отслеживать проблемы, связанные с памятью.
В отличие от таких платформ, как PHP, приложения Node.js представляют собой долгосрочные процессы.
В этом есть ряд положительных моментов — например, возможность один раз подключиться к базе данных и использовать это соединение для всех запросов.
Но эта же особенность может создать проблемы.
Во-первых, давайте взглянем на основы Node.js.
Настоящий австрийский сборщик мусора Node.js — это программа C++, управляемая движком JavaScript V8. Гугл В8 — движок, изначально написанный для Google Chrome, но его можно использовать и автономно.
Поэтому он идеально подходит для Node.js и является, по сути, единственной частью платформы, «понимающей» JavaScript. V8 компилирует JavaScript в машинный код и выполняет его.
Во время выполнения движок управляет выделением и очисткой памяти по мере необходимости.
Это означает, что когда речь идет об управлении памятью в Node.js, мы фактически говорим о V8. Здесь Вы можете увидеть простой пример использования V8 с точки зрения C++.
Схема памяти V8 Работающую программу всегда можно представить через некоторый объем памяти, выделенный в памяти.
Это место называется Resident Set. V8 использует дизайн, аналогичный виртуальной машине Java, и делит память на сегменты: Код: выполняющийся в данный момент код. Куча: содержит все примитивные типы значений (например, целые или логические) с указателями, которые ссылаются на объекты в куче и определяют поток управления программой.
Куча: Сегмент памяти, предназначенный для хранения ссылочных типов, таких как объекты, строки и замыкания.
Схема памяти V8
В Node.js текущее использование памяти можно получить, вызвав процесс.
memoryUsage().
Функция вернет объект, содержащий:
- Размер резидентного набора;
- общий размер кучи;
- объем пространства, используемого в куче.
Использование памяти Node.js с течением времени
Мы видим, что график используемого пространства кучи крайне нестабилен, но всегда остается в определенных пределах, чтобы среднее потребление оставалось постоянным.
Процесс выделения и освобождения памяти называется вывоз мусора.
Введение в сбор мусора Каждой программе, потребляющей память, необходим механизм резервирования и освобождения места.
В C и C++ эта функция выполняется командами malloc() и free(), как показано в примере ниже:
Мы видим, что за освобождение неиспользуемой памяти отвечает программист. Если программа только выделяет память и не освобождает ее, куча будет расти до тех пор, пока не закончится полезная память, что приведет к сбою программы.char * buffer; buffer = (char*) malloc (42); // Do something with buffer free (buffer);
Мы называем его утечка памяти.
Как мы уже знаем, в Node.js JavaScript компилируется в машинный код с использованием V8. Полученные структуры данных после компиляции ничего не могут сделать со своим исходным представлением и V8 просто манипулирует ими.
Это означает, что мы не можем активно выделять и освобождать память в JavaScript. Для решения этой проблемы V8 использует хорошо известный механизм под названием «сборка мусора».
Принцип сборки мусора довольно прост: если на сегмент памяти никто не ссылается, можно считать, что он не используется, и очистить его.
Однако процесс получения и хранения этой информации достаточно сложен, поскольку код может содержать цепочки ссылок и перенаправлений, образующих сложную графовую структуру.
Граф кучи.
Красный объект можно удалить только в том случае, если на него больше нет ссылок.
Сбор мусора — довольно затратный процесс, поскольку он прерывает выполнение приложения, что, естественно, влияет на производительность.
Чтобы исправить эту ситуацию, V8 использует 2 типа сборки мусора:
- Сбор мусора – быстрый, но неполный;
- Mark-Sweep работает относительно медленно, но очищает все неиспользуемые ссылки.
Теперь, взглянув на график, созданный методомprocess.memoryUsage(), вы можете легко различить разные типы сборки мусора: узор, напоминающий зубья пилы, отмечает работу Scavenge, отметки спуска представляют собой Mark-Sweep. Использование встроенного модуля узел-GC-профилер , вы можете получить еще больше информации о том, как работает сборщик мусора.
Модуль подписывается на события сборщика мусора и транслирует их в JavaScript. Возвращаемый объект указывает тип и продолжительность сборки мусора.
Опять же, эти данные можно легко отобразить графически, чтобы было понятнее, как все работает.
Продолжительность и частота запусков сборщика мусора
Хорошо видно, что Scavenge запускается гораздо чаще, чем Mark-Sweep. В зависимости от сложности заявки продолжительность может варьироваться.
Примечательно, что на этом графике можно увидеть частые и кратковременные запуски Mark-Sweep, функция которых мне пока не ясна.
Когда дела идут плохо Если сборщик мусора сам очищает память, чего нам волноваться? Фактически, в ваших журналах могут легко возникнуть утечки памяти.
Исключение утечки памяти
Используя график, который мы создали ранее, мы видим, как память постоянно засоряется!
Прогресс утечки памяти
Сборщик мусора делает все возможное, чтобы освободить память.
Но с каждым запуском мы видим, что потребление памяти постоянно увеличивается, а это явный признак утечки памяти.
Поскольку мы знаем, как точно обнаружить утечку памяти, давайте посмотрим, что нужно сделать, чтобы ее вызвать.
Создание утечки памяти Некоторые утечки очевидны — например, хранение данных в глобальных переменных (например, добавление в массив IP-адресов всех вошедших в систему пользователей).
Другие не так заметны — например, знаменитый Утечка памяти Walmart из-за отсутствия маленькое выражение в основном коде Node.js, на поиск исходного кода которого ушли недели.
Я не буду здесь рассматривать ошибки кода ядра.
Давайте просто посмотрим на труднообнаружимую утечку кода.
из блога Метеор, который вы можете легко разрешить в своем коде.
Внедрение ошибки в ваш код JavaScript
На первый взгляд выглядит нормально.
Вы можете подумать, что theThing перезаписывается каждый раз, когда вызывается replaceThing().
Проблема в том, что someMethod имеет свою собственную область видимости в качестве контекста.
Это означает, что someMethod() знает о unused() и, даже если unused() никогда не вызывается, этот факт не позволит сборщику мусора освободить память от originalThing. Просто потому, что косвенных вызовов слишком много.
Это не ошибка, но она может привести к утечкам памяти, которые будет сложно отследить.
Разве не было бы здорово, если бы вы могли заглянуть в кучу и посмотреть, что там сейчас? К счастью, такая возможность есть! V8 позволяет вам выгружать текущую кучу, а V8-profiler позволяет использовать эту функциональность для JavaScript. /**
* Simple userland heapdump generator using v8-profiler
* Usage: require('[path_to]/HeapDump').
init('datadir') * * @module HeapDump * @type {exports} */ var fs = require('fs'); var profiler = require('v8-profiler'); var _datadir = null; var nextMBThreshold = 0; /** * Init and scheule heap dump runs * * @param datadir Folder to save the data to */ module.exports.init = function (datadir) { _datadir = datadir; setInterval(tickHeapDump, 500); }; /** * Schedule a heapdump by the end of next tick */ function tickHeapDump() { setImmediate(function () { heapDump(); }); } /** * Creates a heap dump if the currently memory threshold is exceeded */ function heapDump() { var memMB = process.memoryUsage().
rss / 1048576; console.log(memMB + '>' + nextMBThreshold); if (memMB > nextMBThreshold) { console.log('Current memory usage: %j', process.memoryUsage()); nextMBThreshold += 50; var snap = profiler.takeSnapshot('profile'); saveHeapSnapshot(snap, _datadir); } } /** * Saves a given snapshot * * @param snapshot Snapshot object * @param datadir Location to save to */ function saveHeapSnapshot(snapshot, datadir) { var buffer = ''; var stamp = Date.now(); snapshot.serialize( function iterator(data, length) { buffer += data; }, function complete() { var name = stamp + '.
heapsnapshot';
fs.writeFile(datadir + '/' + name , buffer, function () {
console.log('Heap snapshot written to ' + name);
});
}
);
}
Этот простой модуль создает файл дампа кучи, если использование памяти продолжает увеличиваться.
Да, существуют гораздо более сложные подходы к выявлению аномалий, но для наших целей этого будет достаточно.
Если есть утечка памяти, у вас может оказаться много таких файлов.
Поэтому вам нужно внимательно следить за этим и добавить в этот модуль функцию оповещения.
Chrome предоставляет ту же функциональность дампа кучи, и вы можете использовать инструменты разработчика Chrome для анализа дампов профилировщика V8.
Инструменты разработчика Chrome
Один дамп кучи может не помочь, поскольку вы не сможете увидеть, как куча меняется с течением времени.
Таким образом, инструменты разработчика Chrome позволяют сравнивать разные файлы.
Сравнивая два дампа, мы получаем дельту значений, показывающую, какие структуры увеличиваются между двумя дампами:
Сравнение дампов показывает нашу утечку
Здесь мы видим нашу проблему.
На переменную, содержащую строку звездочек и называемую longStr, ссылается originalThing, на который ссылается некоторый метод, на который ссылается.
Я думаю, вы поняли идею.
Эта длинная строка вложенных ссылок и контекстов закрытия предотвращает очистку longStr. Хотя этот пример дает очевидные результаты, процесс всегда один и тот же:
- Создайте несколько дампов кучи в разное время и с разными объемами выделенной памяти.
- Сравните несколько дампов, чтобы понять, какие значения растут.
Используя встроенные функции V8 вместе с инструментами разработчика Chrome, вы можете понять, что вызывает утечки памяти, и, если вы встроите эту функцию в свое приложение, у вас будет все необходимое для устранения проблемы, когда она возникнет. Остается один вопрос: как устранить утечку? Ответ прост: просто добавьте theThing = null; до конца функции, и вы сохранены.
Теги: #сборщик мусора #js #разработка #игры #программирование #перевод #разработка сайтов #JavaScript #программирование #node.js
-
Статья О Ноутбуке Sony Vaio Vpc Z124Gx/S
19 Oct, 24 -
Пролог Для Программистов
19 Oct, 24 -
Полиморфная Куайна
19 Oct, 24 -
Вы Служили В Армии?
19 Oct, 24