Анализ памяти и локализация утечки памятиавторНавыки отладки для современной инженерной практики веб-разработкиВ основном он знакомит с методами анализа памяти и обнаружения утечек памяти, которые необходимо понимать в веб-разработке.Индекс данных отладки интерфейса веб-разработки.
Будь то распределенная вычислительная система, серверное приложение или родное приложение iOS или Android, всегда будут проблемы с утечкой памяти, и веб-приложения неизбежно будут иметь аналогичные проблемы. Хотя веб-страницы часто готовы к работе, меньше проблем с определенной веб-страницей, работающей в течение длительного времени, и даже если есть утечка памяти, производительность может быть неочевидной; но на некоторых страницах с отображаемыми данными, которые нужно работать в течение длительного времени. Если утечка памяти не будет устранена вовремя, веб-страница может занимать слишком много памяти, что не только влияет на производительность страницы, но также может привести к сбою всей системы.Еженедельный контрольный список внешнего интерфейсарекомендуемыеHow JavaScript worksЭто серия статей, которые очень хорошо знакомят с операционным механизмом JavaScript. В ней также анализируется управление памятью и утечки памяти. Некоторые изображения и примеры кода в этой статье взяты из этой серии.
Такие языки, как C, обеспечиваютmalloc()
а такжеfree()
Для таких низкоуровневых атомарных операций управления памятью разработчикам необходимо явно и вручную применять и освобождать память; в то время как такие языки, как Java, предоставляют механизм автоматического восстановления памяти, автор находится вОбзор алгоритмов сборки мусора и сборщиков мусора JVMПредставлено в статье. В JavaScript также используется механизм автоматической утилизации памяти, независимо от того, автоматически ли Object, String и т. д. перерабатываются и обрабатываются процессом сборки мусора. Автоматическое освобождение памяти не означает, что мы можем игнорировать операции, связанные с управлением памятью, но может привести к более сложному обнаружению утечек памяти.
выделение и освобождение памяти
АвторДетальный рисунок механизма JavaScript Event LOOP и практическое применение Vue.jsВ этой статье была представлена модель памяти JavaScript, которая в основном состоит из кучи, стека и очереди:
Очередь относится к очереди сообщений, а стек — это стек выполнения функций.Основная структура выглядит следующим образом:
Основные созданные пользователем объекты хранятся в куче, которая также является основной областью, на которую нам нужно обратить внимание при анализе памяти и обнаружении утечек памяти. Так называемая память, с точки зрения аппаратного обеспечения, представляет собой комбинацию бесчисленных триггеров, каждый триггер может хранить 1 бит данных, а разные триггеры расположены по уникальному идентификатору, и разработчики могут читать и записывать данные в соответствии с идентификатором. , спусковой крючок. Абстрактно мы можем думать о памяти как о массиве битов, а данные располагаются в памяти последовательно:
Разработчикам в JavaScript не нужно вручную запрашивать память для объектов, а нужно только объявлять переменные, а среда выполнения JavaScript может автоматически выделять память:
var n = 374; // allocates memory for a number
var s = 'sessionstack'; // allocates memory for a string
var o = {
a: 1,
b: null
}; // allocates memory for an object and its contained values
var a = [1, null, 'str']; // (like object) allocates memory for the
// array and its contained values
function f(a) {
return a + 3;
} // allocates a function (which is a callable object)
// function expressions also allocate an object
someElement.addEventListener('click', function() {
someElement.style.backgroundColor = 'blue';
}, false);
Жизненный цикл памяти объекта делится на три шага: выделение памяти, использование памяти и утилизация памяти.Когда объект больше не нужен, его следует очистить и переработать, за Track отвечает так называемый сборщик мусора, Garbage Collector выделение памяти, определить, полезна ли выделенная память, и автоматически освободить бесполезную память. Большинство сборщиков мусора судят о том, жив объект или нет, основываясь на ссылках.Так называемая ссылка зависит от того, зависит ли объект от других объектов.Если есть зависимость, есть ссылка, например, объект JavaScript ссылается на прототип объект. Самый простой алгоритм сборки мусора — это подсчет ссылок, который очищает все объекты с нулевыми ссылками:
var o1 = {
o2: {
x: 1
}
};
// 2 objects are created.
// 'o2' is referenced by 'o1' object as one of its properties.
// None can be garbage-collected
var o3 = o1; // the 'o3' variable is the second thing that
// has a reference to the object pointed by 'o1'.
o1 = 1; // now, the object that was originally in 'o1' has a
// single reference, embodied by the 'o3' variable
var o4 = o3.o2; // reference to 'o2' property of the object.
// This object has now 2 references: one as
// a property.
// The other as the 'o4' variable
o3 = '374'; // The object that was originally in 'o1' has now zero
// references to it.
// It can be garbage-collected.
// However, what was its 'o2' property is still
// referenced by the 'o4' variable, so it cannot be
// freed.
o4 = null; // what was the 'o2' property of the object originally in
// 'o1' has zero references to it.
// It can be garbage collected.
Однако этот алгоритм часто страдает от проблемы циклической ссылки, когда два бесполезных объекта ссылаются друг на друга:
function f() {
var o1 = {};
var o2 = {};
o1.p = o2; // o1 references o2
o2.p = o1; // o2 references o1. This creates a cycle.
}
f();
Несколько более сложным алгоритмом является так называемый алгоритм Mark-Sweep, который определяет, доступен ли объект на основе того, достижим ли он. Алгоритм пометки и очистки начинается с корневого элемента, такого как объект окна, и проходит вниз по дереву ссылок, помечая все достижимые объекты как доступные и очищая другие неотмеченные объекты.
После 2012 года почти все основные браузеры внедрили сборщики мусора на основе алгоритма маркировки и очистки, и каждый из них также провел целевую оптимизацию.
утечка памяти
Так называемая утечка памяти означает, что объект непреднамеренно добавляется к ссылке, так что, хотя он на самом деле не нужен, его можно обойти и до него можно добраться, так что его память никогда не может быть восстановлена. В этом разделе мы кратко обсудим распространенные ситуации утечки памяти и решения в JavaScript. В новой версии Chrome мы можем использовать Performance Monitor для динамического отслеживания изменений производительности веб-страницы:
Значения индикаторов на рисунке выше:
- CPU usage -Загрузка ЦП текущего сайта;
- JS heap size -Объем памяти приложения;
- DOM Nodes -Количество узлов DOM в памяти;
- JS event listeners-Количество слушателей времени JavaScript, зарегистрированных на текущей странице;
- Documents -Количество файлов стилей или скриптов, используемых на текущей странице;
- Frames -Количество фреймов на текущей странице, включая фреймы и воркеры;
- Layouts / sec -Количество переразметок DOM в секунду;
- Style recalcs / sec -Как часто браузеру нужно пересчитывать стили;
Когда мы обнаружим, что в определенный момент времени может быть утечка памяти, мы можем использовать вкладку «Память», чтобы распечатать выделение кучи в это время:
глобальная переменная
JavaScript обрабатывает все объявленные переменные как глобальные переменные, то есть монтирует их в глобальный объект; глобальным объектом здесь в браузере является окно:
function foo(arg) {
bar = "some text";
}
// 等价于
function foo(arg) {
window.bar = "some text";
}
Другим распространенным способом создания глобальных переменных является неправильное использованиеthis
указатель:
function foo() {
this.var1 = "potential accidental global";
}
// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
Как только переменная смонтирована в объекте окна, это означает, что она всегда доступна. Чтобы избежать этого, мы должны добавить как можно большеuse strict
Или выполните модульное кодирование (см.Краткая история развития модулей JavaScript). Мы также можем расширить функцию сканирования, как показано ниже, для обнаружения и оценки неродных свойств объекта окна:
function scan(o) {
Object.keys(o).forEach(function(key) {
var val = o[key];
// Stop if object was created in another window
if (
typeof val !== "string" &&
typeof val !== "number" &&
typeof val !== "boolean" &&
!(val instanceof Object)
) {
debugger;
console.log(key);
}
// Traverse the nested object hierarchy
});
}
Таймеры и замыкания
мы часто используемsetInterval
Для выполнения задач по времени многие фреймворки также предоставляют механизмы асинхронного выполнения на основе обратного вызова; это может привести к объявлению зависимости от переменной в обратном вызове, например:
var serverData = loadData();
setInterval(function() {
var renderer = document.getElementById('renderer');
if(renderer) {
renderer.innerHTML = JSON.stringify(serverData);
}
}, 5000); //This will be executed every ~5 seconds.
Таймер содержит ссылку на переменную serverData. Если мы не очистим таймер вручную, переменная всегда будет доступна и не будет переработана. Здесь serverData также является замыканием, которое вводится в область обратного вызова setInterval; замыкания также являются одним из распространенных виновников, которые могут привести к утечке памяти:
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing) // a reference to 'originalThing'
console.log("hi");
};
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log("message");
}
};
};
setInterval(replaceThing, 1000);
В приведенном выше коде replaceThing выполняется периодически, создается большой массив и замыкание someMethod назначается Thing. Область видимости someMethod совместно используется с unused , которая, в свою очередь, имеет ссылку на originalThing . Хотя unused на самом деле не используется, метод someMethod объекта Thing может использоваться извне, в результате чего unused всегда доступен. unused, в свою очередь, будет зависеть от Thing в обратном порядке, что в конечном итоге приведет к большому массиву, который невозможно очистить.
Ссылки на DOM и слушатели
Иногда мы можем хранить элементы DOM в структуре данных. Например, когда нам нужно часто обновлять список данных, мы можем хранить используемый список данных в массиве JavaScript, в результате чего каждый элемент DOM существует. в дереве DOM и в массиве JavaScript:
var elements = {
button: document.getElementById('button'),
image: document.getElementById('image')
};
function doStuff() {
elements.image.src = 'http://example.com/image_name.png';
}
function removeImage() {
// The image is a direct child of the body element.
document.body.removeChild(document.getElementById('image'));
// At this point, we still have a reference to #button in the
//global elements object. In other words, the button element is
//still in memory and cannot be collected by the GC.
}
На этом этапе нам нужно удалить все ссылки в дереве DOM и массиве JavaScript, чтобы фактически очистить объект. Точно так же в старых версиях браузеров, если мы очищаем элемент DOM, нам нужно сначала удалить его прослушиватель, иначе браузер не очистит прослушиватель автоматически или не перезапустит прослушиватель, на который ссылается слушатель.
var element = document.getElementById('launch-button');
var counter = 0;
function onClick(event) {
counter++;
element.innerHtml = 'text ' + counter;
}
element.addEventListener('click', onClick);
// Do stuff
element.removeEventListener('click', onClick);
element.parentNode.removeChild(element);
// Now when element goes out of scope,
// both element and onClick will be collected even in old browsers // that don't handle cycles well.
Современные сборщики мусора, используемые современными браузерами, автоматически обнаруживают для нас такие циклические зависимости и удаляют их; сторонние библиотеки, такие как jQuery, также удаляют свои события прослушивателя перед очисткой элемента.
iframe
iframe — это распространенный метод совместного использования интерфейса, но если мы добавим ссылку на объект родительского интерфейса в родительский интерфейс или дочерний интерфейс, например:
// 子页面内
window.top.innerObject = someInsideObject
window.top.document.addEventLister(‘click’, function() { … });
// 外部页面
innerObject = iframeEl.contentWindow.someInsideObject
Некоторые объекты могут остаться после выгрузки iframe (удалить элемент), мы можем выполнить принудительную перезагрузку страницы перед удалением iframe:
<a href="#">Remove</a>
<iframe src="url" />
$('a').click(function(){
$('iframe')[0].contentWindow.location.reload();
// 线上环境实测重置 src 效果会更好
// $('iframe')[0].src = "javascript:false";
setTimeout(function(){
$('iframe').remove();
}, 1000);
});
Или выполните очистку страницы вручную:
window.onbeforeunload = function(){
$(document).unbind().die(); //remove listeners on document
$(document).find('*').unbind().die(); //remove listeners on all nodes
//clean up cookies
/remove items from localStorage
}
Web Worker
В современных браузерах мы часто используем Web Workers для запуска фоновых задач, но иногда, если мы слишком часто и без отказоустойчивости передаем данные между основным потоком и рабочим потоком, это может привести к утечке памяти:
function send() {
setInterval(function() {
const data = {
array1: get100Arrays(),
array2: get500Arrays()
};
let json = JSON.stringify( data );
let arbfr = str2ab (json);
worker.postMessage(arbfr, [arbfr]);
}, 10);
}
function str2ab(str) {
var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
var bufView = new Uint16Array(buf);
for (var i=0, strLen=str.length; i<strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
В реальном коде мы должны проверить, правильно ли работают Transferable Objects:
let ab = new ArrayBuffer(1);
try {
worker.postMessage(ab, [ab]);
if (ab.byteLength) {
console.log('TRANSFERABLE OBJECTS are not supported in your browser!');
}
else {
console.log('USING TRANSFERABLE OBJECTS');
}
}
catch(e) {
console.log('TRANSFERABLE OBJECTS are not supported in your browser!');
}