Memory management

Архитектура потребления памяти

В основе работы рендеринга лежит WebGL-контекст, в котором выделяются ресурсы графического процессора: текстуры, буферы вершин, индексные буферы, framebuffer-объекты. Параллельно JavaScript-слой удерживает структуры данных, связанные со стилем карты, источниками данных, состоянием слоёв и кэшем тайлов.

Ключевая особенность заключается в двойной модели памяти:

  • JavaScript heap — объекты стиля, источники данных, конфигурации, события
  • GPU memory — текстуры тайлов, растровые изображения, шейдеры, геометрия

Неконтролируемый рост любого из слоёв приводит к деградации производительности и утечкам памяти, причём GPU-утечки часто менее очевидны, чем утечки в JS-heap.


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

Экземпляр карты создаётся через new maplibregl.Map(...) и проходит несколько стадий:

  1. Инициализация WebGL-контекста
  2. Загрузка стиля и ресурсов
  3. Построение источников и слоёв
  4. Рендеринг в цикле requestAnimationFrame
  5. Обновление состояния при взаимодействии

Корректное завершение жизненного цикла требует явного освобождения ресурсов. Основной механизм — метод:

map.remove();

При отсутствии вызова remove() WebGL-контекст может остаться активным, что приводит к исчерпанию лимита контекстов браузера.


Освобождение WebGL-контекста

Метод remove() выполняет последовательность операций:

  • остановка render loop
  • удаление обработчиков событий
  • уничтожение WebGL ресурсов
  • освобождение текстур и буферов
  • разрыв связей с DOM контейнером

Критически важный момент — уничтожение WebGL-контекста. Браузеры ограничивают количество активных контекстов, и их утечка приводит к ошибкам вида:

  • too many webgl contexts
  • черный экран вместо карты
  • падение FPS при повторных инициализациях

Утечки при смене стилей

Смена стиля через map.setStyle() инициирует полную перестройку графа слоёв и источников. При некорректной обработке событий загрузки возможны утечки:

  • старые источники продолжают удерживаться в памяти
  • текстуры предыдущего стиля не освобождаются своевременно
  • слушатели styledata, sourcedata накапливаются

Особенно критично использование пользовательских стилей с большим количеством растровых тайлов.

Оптимальная стратегия:

  • избегать параллельных setStyle без ожидания события style.load
  • очищать пользовательские источники перед сменой стиля
  • минимизировать количество динамически добавляемых слоёв

Управление источниками данных

Источники (sources) являются центральным элементом хранения данных карты. Основные типы:

  • vector sources
  • raster sources
  • geojson sources
  • image sources

Каждый источник может удерживать:

  • кэш тайлов
  • декодированные геометрии
  • сетевые буферы
  • изображения в памяти

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

map.getSource('data').setData(geojson);

При больших объёмах данных происходит:

  • рост JS heap из-за копирования объектов
  • увеличение давления на garbage collector
  • временное дублирование геометрии

Для контроля памяти применяется:

  • минимизация частоты setData
  • использование tiled vector sources вместо больших GeoJSON
  • агрегация данных на сервере

Кэш тайлов и его влияние на память

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

  • сетевые ответы (raw data)
  • декодированные геометрические структуры
  • GPU-текстуры для растровых данных
  • готовые буферы рендеринга

Кэширование оптимизирует производительность, но приводит к росту памяти при:

  • быстром перемещении карты
  • зуме в широком диапазоне уровней
  • использовании множества растровых слоёв

Контроль поведения кэша осуществляется через параметры:

  • maxzoom
  • tileCacheBudget
  • tileSize

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


Утечки из-за событий и слушателей

Система событий карты основана на подписках:

map.on('move', handler);

Каждый обработчик удерживает ссылку на замыкания, что влияет на сборку мусора. Типичные причины утечек:

  • повторная инициализация карты без off
  • анонимные функции в on
  • накопление слушателей при React/Vue интеграциях

Проблемная модель:

  • карта пересоздаётся
  • старые обработчики остаются в памяти
  • замыкания удерживают большие объекты состояния

Корректная стратегия:

  • явное удаление слушателей через off
  • хранение ссылок на handler функции
  • привязка жизненного цикла карты к компоненту UI

Работа с изображениями и спрайтами

Sprite atlas и изображения стиля занимают значительную долю GPU памяти. Каждый sprite включает:

  • набор иконок
  • метаданные координат
  • текстуры в GPU памяти

При динамическом добавлении изображений:

map.addImage('icon', imageData);

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

Основные источники утечек:

  • повторное добавление одинаковых изображений
  • отсутствие removeImage
  • частая смена стилей с разными sprite sheets

Рекомендуется:

  • централизованное управление изображениями
  • переиспользование sprite atlas
  • очистка через style.removeImage

Web Workers и декодирование тайлов

Внутренний pipeline MapLibre GL JS использует Web Workers для:

  • парсинга vector tiles
  • декодирования geometry
  • подготовки данных для рендеринга

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

  • main thread — состояние карты и UI
  • worker threads — обработка тайлов

Проблемные сценарии:

  • большое количество одновременных запросов тайлов
  • резкие перемещения карты
  • отсутствие throttling при обновлении источников

Это приводит к росту очередей и увеличению временной памяти.


Оптимизация React/Vue интеграций

При интеграции в SPA-фреймворки основная проблема — пересоздание карты при каждом ререндере компонента.

Типичные утечки:

  • карта создаётся в useEffect без корректного cleanup
  • контейнер DOM пересоздаётся
  • события не удаляются при unmount

Рекомендованная модель жизненного цикла:

  • инициализация один раз
  • хранение экземпляра карты в ref
  • обязательный вызов map.remove() при размонтировании

Мониторинг потребления памяти

Диагностика выполняется через:

  • Chrome DevTools → Memory
  • heap snapshot
  • allocation timeline
  • GPU memory profiler

Сигналы утечек:

  • линейный рост heap без стабилизации
  • увеличение количества WebGL contexts
  • рост retained size после удаления карты
  • падение FPS при стабильной нагрузке

Особое внимание требуется GPU memory, которая не всегда отображается напрямую в JS профайлере.


Практики предотвращения деградации памяти

Устойчивое поведение достигается за счёт следующих принципов:

  • строгое управление жизненным циклом карты
  • минимизация динамических setData
  • контроль количества источников и слоёв
  • ограничение кэша тайлов
  • явное удаление изображений и слоёв при смене стиля
  • отсутствие анонимных обработчиков событий
  • предотвращение повторной инициализации WebGL-контекста

Стабильность памяти в MapLibre GL JS определяется не отдельными оптимизациями, а согласованной дисциплиной управления ресурсами на уровне всей архитектуры приложения.