Память и утечки

OpenLayers строится на сложной системе объектов, которые постоянно создаются и уничтожаются во время взаимодействия с картой. В основе лежит модель наблюдаемых объектов (ol.Observable), которая активно использует подписки на события. Каждая карта, слой, источник данных и большинство интерактивных сущностей являются объектами с жизненным циклом, управляемым вручную через методы очистки.

Ключевой принцип работы памяти в библиотеке — отсутствие автоматического «самоочищения» сложных связей. Если объект был добавлен в карту, он часто продолжает существовать в памяти, пока не разорвана цепочка ссылок: слой → источник → фичи → геометрии → слушатели событий.

Проблемы начинают проявляться не на уровне самой библиотеки, а на уровне неправильного управления ссылками со стороны приложения.


Утечки, связанные с экземпляром карты

ol.Map является центральным объектом, который удерживает:

  • список слоёв;
  • представление (View);
  • DOM-элемент контейнера;
  • обработчики событий;
  • внутренние рендер-пайплайны.

При удалении карты недостаточно убрать DOM-узел из документа. Важно полностью разорвать связи:

  • отключить обработчики;
  • очистить target;
  • освободить ссылки на слои и view.

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

Особенно опасны сценарии, где карта пересоздаётся многократно (SPA-навигация). Если старый экземпляр не «отпущен», каждый новый создаёт параллельную систему рендеринга.


Правильное разрушение карты и разрыв ссылок

Ключевым шагом является отсоединение карты от DOM:

map.setTarget(null);

Этот вызов разрывает связь с контейнером и позволяет рендер-циклу остановиться. Однако этого недостаточно, если остаются внешние ссылки:

  • переменные в модулях;
  • глобальные singleton-объекты;
  • подписки на события карты.

Дополнительно требуется очистка:

  • map.getLayers().clear();
  • map.setView(null) при полном уничтожении;
  • удаление всех слушателей событий.

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


Утечки через события и Observable

Механизм событий в OpenLayers основан на хранении функций-обработчиков в внутренних массивах. Каждый вызов on() добавляет ссылку на callback.

Проблема возникает, когда:

  • обработчики добавляются динамически;
  • отсутствует явное удаление;
  • используются анонимные функции без сохранения ключа.

Пример типичной утечки:

map.on('moveend', () => {
  console.log('move');
});

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

Корректная модель требует сохранения ключа:

const key = map.on('moveend', handler);

и последующего удаления:

unByKey(key);

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


Векторные источники и накопление геометрий

ol/source/Vector и связанные коллекции (ol/Collection) часто становятся источником постепенного роста потребления памяти.

Основные причины:

  • постоянное добавление фич без очистки;
  • отсутствие clear() при обновлении данных;
  • сохранение ссылок на старые объекты вне источника;
  • кэширование геометрий.

Каждая Feature содержит:

  • геометрию;
  • свойства;
  • внутренние наблюдатели изменений.

Даже если фича удалена из слоя, она может оставаться в памяти, если на неё есть внешние ссылки.

Особенно критично использование больших наборов данных в сочетании с частыми обновлениями (например, realtime-трекинг).


Геометрии и скрытые зависимости

Геометрические объекты (Point, LineString, Polygon) могут хранить ссылки на:

  • координатные массивы;
  • трансформированные представления;
  • кэшированные вычисления (длины, площади).

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

Дополнительный источник утечек — привязка геометрий к стилям, где функции стиля замыкают внешние данные.


Утечки через стили и функции рендеринга

Стиль (ol/style/Style) может содержать функции, которые вычисляются при каждом рендере. Если эти функции замыкают внешние переменные, происходит удержание контекста:

style: (feature) => {
  return new Style({
    text: new Text({
      text: externalData.label
    })
  });
}

Если externalData содержит большие структуры или ссылки на компоненты интерфейса, они не освобождаются сборщиком мусора.

Дополнительная проблема — создание новых объектов стиля при каждом вызове вместо их переиспользования.


Tile-системы и кэширование

Тайловые источники (ol/source/XYZ, ol/source/TileWMS) используют кэширование изображений. При этом:

  • тайлы сохраняются в памяти;
  • используется LRU-кэш;
  • объём кэша может расти при отсутствии ограничений.

Утечки возникают, когда:

  • создаётся множество источников без удаления старых;
  • карты переключаются между слоями без очистки;
  • не ограничен размер кэша.

Особенно критично в приложениях с динамическими слоями, где источники пересоздаются при каждом переключении режима.


DOM-утечки и overlays

Overlays (ol/Overlay) часто содержат HTML-элементы, которые могут оставаться в DOM:

  • всплывающие окна;
  • попапы;
  • кастомные контролы.

Если overlay не удалён через setMap(null), DOM-узлы продолжают существовать, даже если карта уничтожена.

Дополнительная проблема — привязка DOM-элементов к внешним обработчикам событий (click, mouseover), которые продолжают удерживать ссылки на функции и данные.


WebGL-рендеринг и графическая память

WebGL-слои используют отдельные буферы GPU. При неправильной очистке:

  • текстуры остаются в памяти видеокарты;
  • буферы вершин не освобождаются;
  • контекст WebGL может удерживаться даже после удаления слоя.

Основной риск возникает при частом создании и уничтожении WebGL-слоёв без явного разрушения ресурсов.


Замыкания и долгоживущие ссылки

Одной из наиболее незаметных причин утечек остаются замыкания JavaScript.

Типичный сценарий:

  • обработчик события карты захватывает переменные UI;
  • компонент уничтожается, но обработчик остаётся;
  • сборщик мусора не может удалить связанные структуры.

Особенно часто это проявляется в SPA-приложениях, где карта интегрирована в React/Vue/Angular-компоненты.


Интеграция с фреймворками и жизненный цикл

При использовании OpenLayers внутри компонентных систем критично соблюдение симметрии:

  • создание карты при mount;
  • полное уничтожение при unmount;
  • удаление всех подписок;
  • сброс источников и слоёв.

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


Слабые ссылки и альтернативные структуры хранения

Для предотвращения утечек применяются структуры:

  • WeakMap для хранения метаданных фич;
  • WeakSet для трекинга объектов;
  • локальные кеши с ограничением размера.

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


Накопление обработчиков и повторная инициализация

Повторная инициализация карты без полного уничтожения предыдущей версии приводит к:

  • удвоению обработчиков;
  • росту числа подписок;
  • дублированию рендер-циклов.

Каждая новая инициализация добавляет слой нагрузки, если старые обработчики не были удалены через unByKey или dispose.


Диагностика утечек и признаки деградации

Косвенные признаки проблем с памятью:

  • постепенное замедление панорамирования карты;
  • рост времени отрисовки тайлов;
  • увеличение потребления памяти в DevTools;
  • повторяющиеся обработчики событий;
  • «залипание» старых объектов на карте.

Эти эффекты часто проявляются не сразу, а после длительной работы интерфейса или многократной смены сцен.


Общие закономерности управления памятью

Поведение OpenLayers в отношении памяти определяется не автоматической очисткой, а дисциплиной управления ссылками:

  • любой добавленный обработчик требует удаления;
  • любой созданный слой требует явного уничтожения;
  • любой источник данных требует очистки при замене;
  • любой DOM-элемент overlay требует отсоединения;
  • любой внешний кэш требует ограничения размера.

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