JavaScript и утечки памяти в Браузере #тред Image
Жизненный цикл памяти практически всегда одинаков и не зависит от ЯП:

- Аллокация памяти (allocate)
- Использование (usage)
- Высвобождение (release)

JavaScript не позволяет напрямую работать с памятью, это происходит опосредованно, через синтаксические конструкции языка
1/2 Аллокация памяти происходит при инициализации значений Image
2/2 Скрытая аллокация памяти через вызовы функций Image
Использование памяти в JavaScript. Еще раз подчеркну, что работа с памятью происходит опосредованно через синтаксис.

Под использованием понимается чтение значений из переменных или свойств объектов, а также запись в них новых значений
Высвобождение памяти в JavaScript происходит с помощью сборщика мусора
1/2 Аллоцированные объекты, можно представить в виде направленного графа, причем c одним корневым элементом. Как правило корень - это глобальный объект. В браузере такой корневой узел графа - это объект window

Для следующего блока кода: Image
2/2 Получится вот такой граф Image
из вики: утечка памяти - процесс неконтролируемого уменьшения объёма свободной оперативной связанный с ошибками в работающих программах

от меня: память, которую высвободили, но не используют, или уже не используемая память согласно решаемой задаче
В ЯП с ручным управлением памятью: выделил память? воспользовался? убери за собой!
Полная ответственность на разработчике

В ЯП с автоматическим управлением памятью за вас уберет сборщик мусора (garbage collector)
Полная ответственность на сборщике мусора.
В ЯП со сборщиками мусора, то что является мусором, зависит исключительно от алгоритмов реализованных в сборщике мусора.

Для понимания этого, к рассмотрению предлагается 2 алгоритма сборки мусора:

* Reference Counting - посчет ссылок
* Mark-Sweep Collector - "пометь и выброси"
1/4

Возьмем следующий блок кода, создадим объекты, создадим ссылку внутри объект, а после "занулим" некоторые переменные Image
2/4

Граф объектов до зануления мы можем представить так, как на картинке

Теперь рассмотрим что здесь будет являться мусором для каждого алгоритма Image
3/4

С точки зрения алгоритма Referece counting, если хотя бы у одного узла есть ссылка, значит его не нужно удалять из памяти, если ссылок нет - это мусор
При таком подходе существует проблема циклических ссылок, разработчик намеренно удалил объект, сборщик не чистит - утечка
3/4 здесь после зануления мусором будет с2
4/4 Для алгоритма mark-and-sweep, мусором будет, те узлы, которые недостижимы из корневого. Для этого производится обход по графу (фаза mark), где помечаются достижимые узлы, после удаляются недостижимые (фаза sweep)

По этому алгоритму мусором будут a1-a2 и c2
4/4 У сборщика мусора с реализацией этого алгоритма больше накладных расходов и есть одна серьезная проблема "проблема остановки мира", мир остановится пока не завершится его работа
Есть еще алгоритмы: Mark-compact garbage collection, Copying garbage collection, Comparing garbage collectors, Generational garbage collection и другие

Разработка сборщика мусора - отдельное искусство с собственным списком решаемых задач
Источников по этой теме немного, но они есть и они хорошие

The Garbage Collection Handbook (платно):
gchandbook.org/contents.html

Курсы и статьи про компиляторы и сборщики мусора у Дмитрия Сошникова: dmitrysoshnikov.com
Вернемся к JavaScript и Браузеру
В современных браузерах сборщики мусора (GC) реализованы на поколениях, используют идею достижимости алгоритма mark-and-sweep и прочее

Например, Webkit: webkit.org/blog/7122/intr… Image
А что нужно знать JavaScript разработчику, помимо достижимости объектов из корневого глобального объекта window?

Сборщик мусора недетерминирован!

Может запуститься в любом момент, его работа не контролируется, не считая возможностей devtools браузера
Дополнение по источникам касательно работы с памятью
Когда-то эмперически было выясненно, что как правило GC запускается после очередной из аллокаций (как сейчас дела обстоят не знаю).
Отсюда можно написать такой JS код, который ни разу не запустит GC. Как? Предварительно аллоцировав все необходимые объекты
html5rocks.com/en/tutorials/s…
Перейдем к практике. У современных браузеров (chrome, safari) существуют инструменты для профилирования этой кучи, а также новых аллокаций.

developers.google.com/web/tools/chro…
Идея поиска утечек на практике проста:
- выделяем пользовательский сценарий
- растет ли память в процессе?
- должна ли она расти?
- ищем объекты, которые аллоцируются
- используются ли они?
- нет? утечка!
Chrome devtools. Performace profiling.

Важным здесь является профилирование Memory. Оно покажется количество объектов в JS Heap, documents, listeners, nodes и тд. Вспомните ранее показанный граф объектов, как стало ясно они бывают разных типов. Image
Помимо возможности вызвать вручную сборку мусора, можно также проследить за работой этого сборщика мусора, в части можно обнаружить фазы его работы. Бывает Major GC, Minor JS и как видно фаза работает не за один раз

На скрине я вручную запустил сборку мусора. Image
Major GC и Minor GC это намек на то, что сборщик мусора в v8 работает на поколениях. В прикреплении детали работы сборщика мусора Orinoco в v8

Trash talk: the Orinoco garbage collector
v8.dev/blog/trash-talk

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

Однако утечка это или нет зависит от того какая задача заложена в скрипт, мб аллокации и подразумеваются задачей. Image
Хорошо, память растет, возможно утечка, что делать дальше? Искать 🙃

Heap Snapshot позволит сделать снимки кучи (место, где все объекты хранятся, вспомните граф) и сравнить снимки м/у собой, этот инструмент позволит понять КАКИЕ объекты "увеличивают память"
Allocation Timeline, другой инструмент, позволит понять КОГДА и ЧТО аллоцировалось

developers.google.com/web/tools/chro… Image
Какие еще есть инструменты, что они делают, как ими пользоваться - читайте в описании Google DevTools
В статьях скриншоты devtools могут быть устаревшими, так как devtools постоянно развивается, однако ничего страшного.

developers.google.com/web/tools/chro…
Обычно выделяют 4 типа утечек связанных с:
- глобальными переменными
- таймерами
- event listener
- замыканиями

Популярная статья об утечках памяти, помимо всего прочего показывает какие типы утечек бывают:
blog.sessionstack.com/how-javascript…
В своем старом доклад про утечки памяти выделял больше типов. Поэтому вопрос типов утечек - вопрос относительный

Слайды: slides.com/xufocoder/memo… Image
Напоследок приведу пример самой простой утечки из доклада.

Напишем сервис кэширования. Также для демонстрации роста памяти будем "сохранять" дополнительные данные.
Создадим его экземпляр и вызовем много раз метод cache Image
График Performace profile, на нем видно несколько "всплексов", это я вручную запускал GC, однако количество памяти (объектов в JS Heap) не уменьшилось, а сервис в примере выше был удален, тоесть память должна очиститься, объекты остались в памяти - нашли утечку. Image
В чем проблема? Проблема в переменной cache она хранит в себе значения, после удаления сервиса, поскольку является глобальным объектом
Решение в лоб, сделать переменную локальной Image
Сделав очередные замеры, и запустив вручную GC, получаем следующий график. После вызовов GC количество объектов в JS Heap возвращается к изначальному уровню. Утечка устранена Image
Как заметил @anber_ru второе решение это использование Weak-* объектов, в нашем случае WeakMap, который будет хранить ссылки слабо, то есть если на объект больше не ссылаются другие объекты, кроме WeakMap, тогда этот объект удаляется

• • •

Missing some Tweet in this thread? You can try to force a refresh
 

Keep Current with jsunderh00d

jsunderh00d Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

PDF

Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

More from @jsunderhood

18 Oct
JavaScript и Браузерные отпечатки #тред Image
Не для кого не секрет, но за интернет пользователями следят, причем давно, а что если я скажу тебе, что это происходит и с помощью браузера тоже Image
Кому нужны мои данные?

Рекламные сети: Google AdSense
Брокеры данных: DoubleClick, ComScore, cXense
Сервисы аналитики: Google Analytics, Yandex metrika
Социальные сети: Facebook, Google Plus

и так далее
Read 26 tweets
17 Oct
Пользуясь случаем хочу лишний раз попиарить несколько источников достойных внимания, откуда можно почерпнуть что-то новое, перенять опыт #тред
Defront
Ведущий: @myshov

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

Telegram - t.me/defront
Website: defront.ru
ДевШахта
Ведущий: @amel_true

Подкаст. Переводы. Веб-разработка.

Помимо всего прочего много материалов касательно NodeJS

Telegram: t.me/devSchachtChan…
Medium: medium.com/devschacht
Youtube youtube.com/channel/UCTSVf…
Read 11 tweets
15 Oct
JavaScript и функциональное программирование #тред
Как известно, JavaScript мультипарадигменный ЯП, позволяет писать код в различных стилях:

* событийно-ориентированный - обработка DOM-событий
* объектно-ориентированный - благодаря прототипному наследованию
* императивный - переменные, выражения, условия, циклы

Что насчет ФП?
Из лекции: Лямбда-исчисление
Read 16 tweets
14 Oct
JavaScript и Браузер #тред
За 30 лет с момента появления 1ого браузера многое изменилось. Браузеры сегодня это очень сложное прикладное ПО. Об этом можно даже судить в 1ом приближении по кол-ву строк кода

How Many Millions of Lines of Code Does It Take (February 8, 2017)
visualcapitalist.com/millions-lines…
Ни для кого не станет открытием, если я напишу, что на текущий момент браузер Google Chrome, является самым популярным, более того, он занимает около 2/3 рынка.

Read 16 tweets
13 Oct
JavaScript и динамическая типизация #тред Image
Если предпосылки слабой типизации в JavaScript были туманны, то решение сделать JavaScript динамическим языком было изначально. Это решение вдохновлено Self, Scheme и испытало на себе влияние Java, C.

Подробнее об этом:
JavaScript: The First 20 Years. dl.acm.org/doi/pdf/10.114…
Тяжело дать однозначное трактование динамической типизации, но можно через сравнение со статической. Переменные связываются с типов в момент объявления переменной - статическая типизация, при присваивании значения - динамическая типизация.
Read 18 tweets
4 Jul
Давайте попробуем старую практику как мир: лайк этому посту от вас, факт про эффектор, его историю и концепции от меня. Посмотрим насколько меня хватит!
Изначально effector был миддлварой для ридакса и разрабатывался для высоконагруженного клиента реалтайм чата, вроде Телеграм.
Пока флоу не умер, имелась его первоклассная поддержка. Сейчас типы затачиваются под TypeScript.
Read 33 tweets

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!

Follow Us on Twitter!