Жизненный цикл памяти практически всегда одинаков и не зависит от ЯП:
- Аллокация памяти (allocate)
- Использование (usage)
- Высвобождение (release)
JavaScript не позволяет напрямую работать с памятью, это происходит опосредованно, через синтаксические конструкции языка
1/2 Аллокация памяти происходит при инициализации значений
2/2 Скрытая аллокация памяти через вызовы функций
Использование памяти в JavaScript. Еще раз подчеркну, что работа с памятью происходит опосредованно через синтаксис.
Под использованием понимается чтение значений из переменных или свойств объектов, а также запись в них новых значений
Высвобождение памяти в JavaScript происходит с помощью сборщика мусора
1/2 Аллоцированные объекты, можно представить в виде направленного графа, причем c одним корневым элементом. Как правило корень - это глобальный объект. В браузере такой корневой узел графа - это объект window
Для следующего блока кода:
2/2 Получится вот такой граф
из вики: утечка памяти - процесс неконтролируемого уменьшения объёма свободной оперативной связанный с ошибками в работающих программах
от меня: память, которую высвободили, но не используют, или уже не используемая память согласно решаемой задаче
В ЯП с ручным управлением памятью: выделил память? воспользовался? убери за собой!
Полная ответственность на разработчике
В ЯП с автоматическим управлением памятью за вас уберет сборщик мусора (garbage collector)
Полная ответственность на сборщике мусора.
В ЯП со сборщиками мусора, то что является мусором, зависит исключительно от алгоритмов реализованных в сборщике мусора.
Для понимания этого, к рассмотрению предлагается 2 алгоритма сборки мусора:
Возьмем следующий блок кода, создадим объекты, создадим ссылку внутри объект, а после "занулим" некоторые переменные
2/4
Граф объектов до зануления мы можем представить так, как на картинке
Теперь рассмотрим что здесь будет являться мусором для каждого алгоритма
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 и другие
Разработка сборщика мусора - отдельное искусство с собственным списком решаемых задач
Источников по этой теме немного, но они есть и они хорошие
Когда-то эмперически было выясненно, что как правило GC запускается после очередной из аллокаций (как сейчас дела обстоят не знаю).
Отсюда можно написать такой JS код, который ни разу не запустит GC. Как? Предварительно аллоцировав все необходимые объекты html5rocks.com/en/tutorials/s…
Перейдем к практике. У современных браузеров (chrome, safari) существуют инструменты для профилирования этой кучи, а также новых аллокаций.
Идея поиска утечек на практике проста:
- выделяем пользовательский сценарий
- растет ли память в процессе?
- должна ли она расти?
- ищем объекты, которые аллоцируются
- используются ли они?
- нет? утечка!
Chrome devtools. Performace profiling.
Важным здесь является профилирование Memory. Оно покажется количество объектов в JS Heap, documents, listeners, nodes и тд. Вспомните ранее показанный граф объектов, как стало ясно они бывают разных типов.
Помимо возможности вызвать вручную сборку мусора, можно также проследить за работой этого сборщика мусора, в части можно обнаружить фазы его работы. Бывает Major GC, Minor JS и как видно фаза работает не за один раз
На скрине я вручную запустил сборку мусора.
Major GC и Minor GC это намек на то, что сборщик мусора в v8 работает на поколениях. В прикреплении детали работы сборщика мусора Orinoco в v8
Если график профилирования памяти выглядит так (картинка), значит все хорошо, память аллоцируется, затем срабатывает сборщик мусора.
Общее значение используемой памяти колеблется около одного уровня - утечки памяти нет.
Если график профилирования памяти выглядит так (картинка), GC пытается чистить, но общий уровень растет, значит возможна есть утечка памяти.
Однако утечка это или нет зависит от того какая задача заложена в скрипт, мб аллокации и подразумеваются задачей.
Хорошо, память растет, возможно утечка, что делать дальше? Искать 🙃
Heap Snapshot позволит сделать снимки кучи (место, где все объекты хранятся, вспомните граф) и сравнить снимки м/у собой, этот инструмент позволит понять КАКИЕ объекты "увеличивают память"
Allocation Timeline, другой инструмент, позволит понять КОГДА и ЧТО аллоцировалось
Какие еще есть инструменты, что они делают, как ими пользоваться - читайте в описании Google DevTools
В статьях скриншоты devtools могут быть устаревшими, так как devtools постоянно развивается, однако ничего страшного.
Напоследок приведу пример самой простой утечки из доклада.
Напишем сервис кэширования. Также для демонстрации роста памяти будем "сохранять" дополнительные данные.
Создадим его экземпляр и вызовем много раз метод cache
График Performace profile, на нем видно несколько "всплексов", это я вручную запускал GC, однако количество памяти (объектов в JS Heap) не уменьшилось, а сервис в примере выше был удален, тоесть память должна очиститься, объекты остались в памяти - нашли утечку.
В чем проблема? Проблема в переменной cache она хранит в себе значения, после удаления сервиса, поскольку является глобальным объектом
Решение в лоб, сделать переменную локальной
Сделав очередные замеры, и запустив вручную GC, получаем следующий график. После вызовов GC количество объектов в JS Heap возвращается к изначальному уровню. Утечка устранена
Как заметил @anber_ru второе решение это использование Weak-* объектов, в нашем случае WeakMap, который будет хранить ссылки слабо, то есть если на объект больше не ссылаются другие объекты, кроме WeakMap, тогда этот объект удаляется
Не для кого не секрет, но за интернет пользователями следят, причем давно, а что если я скажу тебе, что это происходит и с помощью браузера тоже
Кому нужны мои данные?
Рекламные сети: Google AdSense
Брокеры данных: DoubleClick, ComScore, cXense
Сервисы аналитики: Google Analytics, Yandex metrika
Социальные сети: Facebook, Google Plus
За 30 лет с момента появления 1ого браузера многое изменилось. Браузеры сегодня это очень сложное прикладное ПО. Об этом можно даже судить в 1ом приближении по кол-ву строк кода
Ни для кого не станет открытием, если я напишу, что на текущий момент браузер Google Chrome, является самым популярным, более того, он занимает около 2/3 рынка.
Если предпосылки слабой типизации в JavaScript были туманны, то решение сделать JavaScript динамическим языком было изначально. Это решение вдохновлено Self, Scheme и испытало на себе влияние Java, C.
Тяжело дать однозначное трактование динамической типизации, но можно через сравнение со статической. Переменные связываются с типов в момент объявления переменной - статическая типизация, при присваивании значения - динамическая типизация.
Давайте попробуем старую практику как мир: лайк этому посту от вас, факт про эффектор, его историю и концепции от меня. Посмотрим насколько меня хватит!
Изначально effector был миддлварой для ридакса и разрабатывался для высоконагруженного клиента реалтайм чата, вроде Телеграм.
Пока флоу не умер, имелась его первоклассная поддержка. Сейчас типы затачиваются под TypeScript.