Как очистить память javascript

Обновлено: 02.07.2024

Низкоуровневые языки программирования (например, C) имеют низкоуровневые примитивы для управления памятью, такие как malloc() и free() . В JavaScript же память выделяется динамически при создании сущностей (объектов, строк и т. п.) и "автоматически" освобождается, когда они больше не используются. Последний процесс называется сборкой мусора. Слово "автоматически" является источником путаницы и зачастую создаёт у программистов на JavaScript (и других высокоуровневых языках) ложное ощущение, что они могут не заботиться об управлении памятью.

Жизненный цикл памяти¶

Независимо от языка программирования, жизненный цикл памяти практически всегда один и тот же:

  1. Выделение необходимой памяти.
  2. Её использование (чтение, запись).
  3. Освобождение выделенной памяти, когда в ней более нет необходимости.

Первые два пункта осуществляются явным образом программистом во всех языках программирования. Третий пункт осуществляется явным образом в низкоуровневых языках, но в большинстве высокоуровневых языков, в том числе и в JavaScript, осуществляется автоматически.

Выделение памяти в JavaScript¶

Чтобы не утруждать программиста заботой о низкоуровневых операциях выделения памяти, интерпретатор JavaScript динамически выделяет необходимую память при объявлении переменных:

Вызовы некоторых функций также ведут к выделению памяти под объект:

Некоторые методы выделяют память для новых значений или объектов:

Использование значений¶

Использование значений, как правило, означает - чтение и запись значений из/в выделенной для них области памяти. Это происходит при чтении или записи значения какой-либо переменной, или свойства объекта или даже при передаче аргумента функции.

Освобождение памяти, когда она более не нужна¶

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

Интерпретаторы языков высокого уровня снабжаются встроенным программным обеспечением под названием "сборщик мусора", задачей которого является следить за выделением и использованием памяти и при необходимости автоматически освобождать более не нужные участки памяти. Это происходит весьма приблизительно, так как основная проблема точного определения того момента, когда какая-либо часть памяти более не нужна - неразрешима (данная проблема не поддаётся однозначному алгоритмическому решению).

Сборка мусора¶

Достижимость¶

Основной концепцией управления памятью в JavaScript является принцип достижимости.

Если упростить, то "достижимые" значения - это те, которые доступны или используются. Они гарантированно находятся в памяти.

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

  • Локальные переменные и параметры текущей функции.
  • Переменные и параметры других функций в текущей цепочке вложенных вызовов.
  • Глобальные переменные.
  • (некоторые другие внутренние значения)

Эти значения мы будем называть корнями.

Любое другое значение считается достижимым, если оно доступно из корня по ссылке или по цепочке ссылок.

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

В интерпретаторе JavaScript есть фоновый процесс, который называется сборщик мусора. Он следит за всеми объектами и удаляет те, которые стали недостижимы.

Вот самый простой пример:

Здесь стрелка обозначает ссылку на объект. Глобальная переменная user ссылается на объект (мы будем называть его просто "John"). В свойстве "name" объекта John хранится примитив, поэтому оно нарисовано внутри объекта.

Если перезаписать значение user , то ссылка потеряется:

Теперь объект John становится недостижимым. К нему нет доступа, на него нет ссылок. Сборщик мусора удалит эти данные и освободит память.

Две ссылки¶

Представим, что мы скопировали ссылку из user в admin :

Теперь, если мы сделаем то же самое:

. то объект John всё ещё достижим через глобальную переменную admin , поэтому он находится в памяти. Если бы мы также перезаписали admin , то John был бы удалён.

Взаимосвязанные объекты¶

Теперь более сложный пример. Семья:

Функция marry "женит" два объекта, давая им ссылки друг на друга, и возвращает новый объект, содержащий ссылки на два предыдущих.

В результате получаем такую структуру памяти:

На данный момент все объекты достижимы.

Теперь удалим две ссылки:

Недостаточно удалить только одну из этих ссылок, потому что все объекты останутся достижимыми.

Но если мы удалим обе, то увидим, что у объекта John больше нет входящих ссылок:

Исходящие ссылки не имеют значения. Только входящие ссылки могут сделать объект достижимым. Объект John теперь недостижим и будет удалён из памяти со всеми своими данными, которые также стали недоступны.

После сборки мусора:

Недостижимый "остров"¶

Вполне возможна ситуация, при которой целый "остров" связанных объектов может стать недостижимым и удалиться из памяти.

Возьмём объект family из примера выше. А затем:

Структура в памяти теперь станет такой:

Этот пример демонстрирует, насколько важна концепция достижимости.

Объекты John и Ann всё ещё связаны, оба имеют входящие ссылки, но этого недостаточно.

У объекта family больше нет ссылки от корня, поэтому весь "остров" становится недостижимым и будет удалён.

Внутренние алгоритмы¶

Основной алгоритм сборки мусора - "алгоритм пометок" (англ. "mark-and-sweep").

Согласно этому алгоритму, сборщик мусора регулярно выполняет следующие шаги:

  • Сборщик мусора "помечает" (запоминает) все корневые объекты.
  • Затем он идёт по их ссылкам и помечает все найденные объекты.
  • Затем он идёт по ссылкам помеченных объектов и помечает объекты, на которые есть ссылка от них. Все объекты запоминаются, чтобы в будущем не посещать один и тот же объект дважды.
  • . И так далее, пока не будут посещены все ссылки (достижимые от корней).
  • Все непомеченные объекты удаляются.

Например, пусть наша структура объектов выглядит так:

Явно виден "недостижимый остров" справа. Теперь посмотрим, как будет работать "алгоритм пометок" сборщика мусора.

На первом шаге помечаются корни:

Затем помечаются объекты по их ссылкам:

. а затем объекты по их ссылкам и так далее, пока это вообще возможно:

Теперь объекты, до которых не удалось дойти от корней, считаются недостижимыми и будут удалены:

Это и есть принцип работы сборки мусора.

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

Вот некоторые из оптимизаций:

  • Сборка по поколениям (Generational collection) - объекты делятся на "новые" и "старые". Многие объекты появляются, выполняют свою задачу и быстро умирают, их можно удалять более агрессивно. Те, которые живут достаточно долго, становятся "старыми" и проверяются реже.
  • Инкрементальная сборка (Incremental collection) - если объектов много, то обход всех ссылок и пометка достижимых объектов может занять значительное время и привести к видимым задержкам выполнения скрипта. Поэтому интерпретатор пытается организовать сборку мусора поэтапно. Этапы выполняются по отдельности один за другим. Это требует дополнительного учёта для отслеживания изменений между этапами, но зато теперь у нас есть много крошечных задержек вместо одной большой.
  • Сборка в свободное время (Idle-time collection) - чтобы уменьшить возможное влияние на производительность, сборщик мусора старается работать только во время простоя процессора.

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

Итого¶

Главное из того, что мы узнали:

  • Сборка мусора выполняется автоматически. Мы не можем ускорить или предотвратить её.
  • Объекты сохраняются в памяти, пока они достижимы.
  • Наличие ссылки не гарантирует, что объект достижим (от корня): несколько взаимосвязанных объектов могут стать недостижимыми как единое целое.

Современные интерпретаторы реализуют передовые алгоритмы сборки мусора.

Некоторые из них освещены в книге "The Garbage Collection Handbook: The Art of Automatic Memory Management" (R. Jones и др.).

Если вы знакомы с низкоуровневым программированием, то более подробная информация о сборщике мусора интерпретатора V8 находится в статье A tour of V8: Garbage Collection.

редактировать Казалось бы, хотя удаление X. obj не сразу очистит память, это поможет сбору мусора. Если я не удалю X. obj, все равно будет указатель на объект, и поэтому GC не может его очистить.

хотя я выбираю ответ @ delnan, если Вы читаете это, вы также должны def поймать статье Benubird по.

Я также заметил, что я случайно написал delete(X) первоначально вместо delete (X. obj) - извините.

короткий ответ заключается в том, что вы не. delete просто удаляет ссылку (а не так, как вы пытаетесь использовать его, см. ссылку выше - delete является одной из тех особенностей языка, которые мало кто понимает), не более того. Реализация очищает память для вас, но это не ваше дело, когда (и даже если, строго говоря, именно поэтому не следует полагаться на финализаторы на языках GC'D, которые их предлагают) это делает. Обратите внимание:

  • только объекты, которые могут быть доказано, что недоступен (т. е. нет способа получить к нему доступ) для всего кода может быть удален. То, что держит ссылки на кого, обычно довольно очевидно, по крайней мере концептуально. Вам просто нужно следить при работе с большим количеством замыканий, так как они могут захватывать больше переменных, чем вы думаете. Также обратите внимание, что круговые ссылки are очистка.
  • есть ошибка в старых (но, к сожалению, все еще используемых) версиях IE, включающих сборку мусора обработчиков событий JS и элементов DOM. Гуглить (возможно, даже так) должен иметь лучший материал о моей памяти.

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

нет - Javascript запускает GC, когда ему это нравится.

метод Delete удаляет только ссылку, а не объект. Любые другие ссылки будут оставлены в открытом ожидании сборщика мусора.

JavaScript имеет свой собственный GC, и он будет бегать и убирать вещи, когда ничто больше не относится к ним.

Я все еще думаю, что это хорошая практика для нулевых объектов. Удаление объекта также помогает GC, потому что он увидит что-то болтающееся и скажет: "Я собираюсь съесть тебя, потому что ты совсем один (и теперь некоторые циничный смех)".

даже если есть GC, вы все равно хотите, чтобы ваш скрипт был оптимизирован для производительности, поскольку компьютеры, браузеры и панели инструментов (и их количество) будут отличаться.

вообще говоря, управление памятью в Javascript зависит от пользователя-агента. Основы сборщика мусора - подсчет ссылок. Итак, установив ссылку на null (используя delete ключевое слово или явное назначение), вы можете убедиться, что ссылка будет очищена,Если объект не имеет ссылок, которые будут жить за пределами его области создания. В этом случае GC уже очистит любые объекты или переменные чья область завершилась без явного задания значения null.

есть некоторые вещи, о которых нужно позаботиться, хотя - круговые ссылки легко создать в JS, особенно между элементом DOM и объектом. Необходимо позаботиться о том, чтобы очистить (или не создавать в первую очередь) ссылки на и/или из элементов DOM внутри объектов. Если вы создадите ссылку to/from, связанную с DOM, обязательно явно очистите их, установив ссылки на null-как для вашего объекта, так и для элемент DOM. Просто установить родительский объект в значение null недостаточно, если есть дочерние объекты со ссылками на / из DOM или localStorage, потому что эти ссылки будут жить, и если была какая-либо ссылка от ребенка к родителю, то родитель будет жить в памяти из-за этой ссылки.

веб-страницы могут на самом деле утечка мусора в памяти таким образом-после перехода, круговые ссылки хранят объекты и элементы DOM в памяти до перезапуска браузер!

память JavaScript обычно обрабатывается аналогично Java-я имею в виду, что есть (или должен быть) сборщик мусора, который удалит объект, если на него нет ссылок. Так что да, просто "аннулирование" ссылки-это единственный способ "обрабатывать" освобождающую память, а реальное освобождение-это хост-часть JS.

  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents Loading

Copy raw contents

Copy raw contents

Утечки памяти происходят, когда браузер по какой-то причине не может освободить память от недостижимых объектов.

Обычно это происходит автоматически (info:memory-management). Кроме того, браузер освобождает память при переходе на другую страницу. Поэтому утечки в реальной жизни проявляют себя в двух ситуациях:

Утечки бывают из-за ошибок браузера, ошибок в расширениях браузера и, гораздо реже, по причине ошибок в архитектуре JavaScript-кода. Мы разберём несколько наиболее частых и важных примеров.

Коллекция утечек в IE

IE до версии 8 не умел очищать циклические ссылки, появляющиеся между DOM-объектами и объектами JavaScript. В результате и DOM и JS оставались в памяти навсегда.

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

Без привязки метода method к элементу здесь утечки не возникнет.

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

Утечка IE8 при обращении к коллекциям таблицы

Эта утечка происходит только в IE8 в стандартном режиме. В нём при обращении к табличным псевдо-массивам (напр. rows ) создаются и не очищаются внутренние ссылки, что приводит к утечкам.

Также воспроизводится в новых IE в режиме совместимости с IE8.

Полный пример (IE8):

  • Если убрать отмеченную строку, то утечки не будет.
  • Если заменить строку (*) на elem.innerHTML = '' , то память будет очищена, т.к. этот способ работает по-другому, нежели просто removeChild (см. главу info:memory-management).
  • Утечка произойдёт не только при доступе к rows , но и к другим свойствам, например elem.firstChild.tBodies[0] .

Эта утечка проявляется, в частности, при удалении детей элемента следующей функцией:

Если идёт доступ к табличным коллекциям и регулярное обновление таблиц при помощи DOM-методов -- утечка в IE8 будет расти.

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

Как вы думаете, почему? Если вы внимательно читали то, что написано выше, то имеете информацию для ответа на этот вопрос..

Посмотрим, какая структура памяти создается при каждом запуске:


Когда запускается асинхронный запрос xhr , браузер создаёт специальную внутреннюю ссылку (internal reference) на этот объект и будет поддерживать её, пока он находится в процессе коммуникации. Именно поэтому объект xhr будет жив после окончания работы функции.

Полный пример (IE8):


Теперь циклической ссылки нет -- и не будет утечки.

Объемы утечек памяти

Объем "утекающей" памяти может быть небольшим. Тогда это почти не ощущается. Но так как замыкания ведут к сохранению переменных внешних функций, то одна функция может тянуть за собой много чего ещё.

Представьте, вы создали функцию, и одна из ее переменных содержит очень большую по объему строку (например, получает с сервера).

Пока функция inner остается в памяти, LexicalEnvironment с переменной большого объема внутри висит в памяти.

Висит до тех пор, пока функция inner жива.

Как правило, JavaScript не знает, какие из переменных функции inner будут использованы, поэтому оставляет их все. Исключение -- виртуальная машина V8 (Chrome, Opera, Node.JS), она часто (не всегда) видит, что переменная не используется во внутренних функциях, и очистит память.

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

Сэкономить память здесь вполне можно. Мы же знаем, что переменная data не используется в inner . Поэтому просто обнулим её:

Поиск и устранение утечек памяти

Проверка на утечки

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

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

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

  • Если речь об IE, то надо смотреть "Виртуальную память" в списке процессов, а не только обычную "Память". Обычная может очищаться за счет того, что перемещается в виртуальную (на диск).
  • Для простоты отладки, если есть подозрение на утечку конкретных объектов, в них добавляют большие свойства-маркеры. Например, подойдет фрагмент текста: new Array(999999).join('leak') .

Утечки могут возникать из-за расширений браузера, взаимодействющих со страницей. Еще более важно, что утечки могут быть следствием конфликта двух браузерных расширений Например, было такое: память текла когда включены расширения Skype и плагин антивируса одновременно.

Чтобы понять, в расширениях дело или нет, нужно отключить их:

  1. Отключить Flash.
  2. Отключить антивирусную защиту, проверку ссылок и другие модули, и дополнения.
  3. Отключить плагины. Отключить ВСЕ плагины.
  • Для IE есть параметр коммандной строки:


  • Firefox необходимо запускать с чистым профилем. Используйте следующую команду для запуска менеджера профилей и создания чистого пустого профиля:

Пожалуй, единственный браузер с поддержкой отладки памяти -- это Chrome. В инструментах разработчика вкладка Timeline -- Memory показывает график использования памяти.


Можем посмотреть, сколько памяти используется и на что.

Также в Profiles есть кнопка Take Heap Snapshot, здесь можно сделать и исследовать снимок текущего состояния страницы. Снимки можно сравнивать друг с другом, выяснять количество новых объектов. Можно смотреть, почему объект не очищен и кто на него ссылается.

Замечательная статья на эту тему есть в документации: Chrome Developer Tools: Heap Profiling.

Утечки памяти штука довольно сложная. В борьбе с ними вам определенно понадобится одна вещь: Удача!

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

Повышение количества SPA (Single Page Application - одностраничное приложение) побуждает нас уделять больше внимания хорошим методам кодирования, связанным с памятью. Использование SPA подразумевает нахождение на одной странице в течение гораздо более длительного времени. Если страница, которая никогда не перезагружается полностью, начинает постепенно использовать все больше и больше памяти, это может серьезно повлиять на производительность и даже вызвать сбой вкладки браузера.

В этой статье мы рассмотрим шаблоны программирования, которые вызывают утечки памяти в JavaScript, и объясним, как улучшить управление памятью.

Что такое утечка памяти и как ее обнаружить?

Браузер хранит объекты в памяти, в то время как они могут быть доступны из корня скрипта через цепочку ссылок. Сборщик мусора (Garbage Collector) - это фоновый процесс в движке JavaScript, который идентифицирует недоступные объекты, удаляет их и освобождает основную память.

Пример цепочки ссылок от корня сборщика мусора к объектам

Пример цепочки ссылок от корня сборщика мусора к объектам

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

Недоступность объекта

Объект 4 недоступен и будет удален из памяти. Объект 3 по-прежнему доступен через забытую ссылку из объекта 2 и не будет собирать мусор.

Как выяснить, что в нашем коде есть утечки памяти?

Ну, утечки памяти хитрые, их часто трудно заметить и локализовать. Утечка кода JavaScript ни в коем случае не считается ошибкой, поэтому браузер тоже не выдает никакой ошибки при своем запуске. Если мы заметим, что производительность нашей страницы постепенно ухудшается, встроенные в браузер инструменты могут помочь нам определить, существует ли утечка памяти, и какие объекты вызывают ее.

Самый быстрый способ проверки использования памяти - взглянуть на диспетчеры задач браузера (не путать с диспетчером задач операционной системы). Они предоставляют нам обзор всех вкладок и процессов, запущенных в настоящее время в браузере. Чтобы открыть диспетчер задач Chrome, нажмите Shift + Esc в Linux и Windows, а в диспетчер Firefox введите about:performance в адресной строке.

Помимо прочего, эти инструменты позволяют нам видеть объем памяти JavaScript каждой вкладки. Если наш сайт просто сидит и ничего не делает, но, тем не менее, использование памяти JavaScript постепенно увеличивается, есть большая вероятность, что у нас происходит утечка памяти.

Инструменты разработчика (Developer Tools) предоставляют более продвинутые методы управления памятью. С помощью инструмента производительности Chrome мы можем визуально анализировать производительность страницы во время ее работы. Некоторые шаблоны типичны для утечек памяти, например, схема увеличения использования динамической памяти, показанная ниже.

потребление памяти в Chrome

Производительность записи в Chrome - потребление памяти постоянно растет (синяя линия)

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

Общие источники утечек памяти в коде JavaScript

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

1. Случайные глобальные переменные

Глобальные переменные всегда доступны из корня скрипта и никогда не будут собраны как мусор с помощью Garbage Collector. Некоторые ошибки вызывают утечку переменных из локальной области в глобальную область в нестрогом режиме:

Читайте также: