Memory leaks поиск

Природа утечек памяти в WebGL-контексте CesiumJS

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

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

  • EntityCollection
  • PrimitiveCollection
  • DataSourceDisplay
  • ImageryLayerCollection
  • Tile caches (Cesium3DTileset)

Утечка может проявляться как постепенный рост RAM и VRAM при неизменной сцене или при повторных переходах между состояниями приложения.


Типовые источники утечек

Entity и EntityCollection

Добавление сущностей через viewer.entities.add() без последующего удаления приводит к накоплению ссылок в коллекции.

Особенно критичны случаи:

  • динамическое создание большого числа объектов без remove или removeAll
  • привязка реактивных свойств (CallbackProperty), которые продолжают вычисляться
  • вложенные ссылки на внешние замыкания

Проблема усугубляется, если сущности содержат MaterialProperty с анимацией.


ScreenSpaceEventHandler

Одним из наиболее частых источников утечек является обработчик событий:

  • new Cesium.ScreenSpaceEventHandler(scene.canvas)
  • setInputAction(...)

Если обработчик не уничтожается через destroy(), он продолжает удерживать ссылки на scene и viewer.

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


Cesium3DTileset и кэш тайлов

3D Tiles используют агрессивное кэширование:

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

Даже после удаления tileset из сцены:

viewer.scene.primitives.remove(tileset);

без вызова:

tileset.destroy();

часть ресурсов может остаться в памяти.

Дополнительно важно учитывать tileset.maximumMemoryUsage и кэш плиток в TileCache.


ImageryLayer и текстуры

Imagery layers часто создаются и заменяются динамически:

  • спутниковые подложки
  • кастомные WMTS/XYZ источники

Удаление слоя без явного освобождения:

viewer.imageryLayers.remove(layer);

не гарантирует мгновенное освобождение WebGL-текстур. При интенсивной смене слоёв происходит накопление GPU памяти.


PrimitiveCollection и Custom Primitives

Low-level primitives:

  • GeometryInstance
  • Primitive
  • GroundPrimitive

могут удерживать буферы GPU даже после удаления из коллекции, если:

  • не вызван destroy()
  • остались ссылки в пользовательских структурах

Timers, requestAnimationFrame и замыкания

Утечки на уровне JavaScript часто связаны не с Cesium напрямую:

  • setInterval без очистки
  • requestAnimationFrame в циклических обновлениях
  • подписки на Observable без отписки

Особенно опасны замыкания, содержащие ссылки на viewer, scene или entity.


Инструменты диагностики утечек

Chrome DevTools Heap Snapshot

Основной инструмент анализа:

  • фиксирование базового состояния
  • выполнение сценария (например, переходы сцен, загрузка tileset)
  • повторный snapshot
  • сравнение retainers

Ключевые признаки утечки:

  • рост Detached DOM Tree
  • увеличение количества Cesium объектов (Entity, Cesium3DTileset, Texture)
  • цепочки удержания через closures

Allocation Timeline

Позволяет выявить:

  • непрерывное выделение памяти при отсутствии пользовательской активности
  • периодические аллокации, связанные с анимациями Cesium

Особенно полезно для выявления утечек в requestRenderMode выключенном режиме.


Performance Monitor CesiumJS

Встроенный инструмент:

  • отображение FPS
  • количество draw calls
  • загрузка текстур
  • количество primitives

Активируется через:

viewer.scene.debugShowFramesPerSecond = true;

Дополнительно можно отслеживать:

  • viewer.scene.globe.tilesLoaded
  • количество активных tilesets
  • размер кэша imagery

Методы локализации проблемных участков

Изоляция сцен

Разделение приложения на независимые сценарии:

  • только entities
  • только tilesets
  • только imagery

Позволяет определить подсистему, вызывающую рост памяти.


Проверка жизненного цикла объектов

Каждый Cesium-объект должен проверяться на наличие метода:

  • destroy()
  • isDestroyed()

Пример типовой ошибки:

const handler = new Cesium.ScreenSpaceEventHandler(canvas);
// отсутствие handler.destroy()

Контроль подписок

Часто утечки возникают из-за:

  • viewer.camera.changed.addEventListener
  • scene.preRender.addEventListener
  • clock.onTick

Без явного удаления через removeEventListener.


Типовые паттерны исправления утечек

Явное уничтожение Viewer

При разрушении приложения:

viewer.destroy();

Это освобождает:

  • сцену
  • рендерер
  • события
  • кэши

Отсутствие вызова приводит к накоплению WebGL контекстов при повторной инициализации.


Очистка коллекций
viewer.entities.removeAll();
viewer.scene.primitives.removeAll();
viewer.imageryLayers.removeAll();

При этом важно учитывать, что removeAll не всегда вызывает destroy у вложенных объектов.


Управление tilesets

Корректный жизненный цикл:

viewer.scene.primitives.remove(tileset);
tileset.destroy();

Также требуется обнуление ссылок:

tileset = null;

Отписка от событий
handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
handler.destroy();
handler = null;

Анализ GPU памяти

CesiumJS использует WebGL, поэтому утечки могут быть скрыты в виде:

  • неосвобождённых текстур
  • vertex buffer objects (VBO)
  • framebuffers

Особенности:

  • DevTools показывает только часть VRAM
  • утечки могут проявляться как “плато” памяти без освобождения

Дополнительный индикатор — рост GPU Memory в Chrome Task Manager.


Частые архитектурные ошибки

Повторная инициализация Viewer

Создание нового viewer без уничтожения старого:

  • приводит к накоплению WebGL контекстов
  • каждый контекст имеет ограничение браузера

Глобальные ссылки на Cesium объекты

Хранение объектов в:

  • глобальных singleton
  • state менеджерах без очистки
  • кешах без TTL

Анимационные CallbackProperty

Неправильно реализованные callback-и:

  • удерживают замыкания
  • выполняются каждый кадр
  • не освобождаются при удалении entity

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

  1. Зафиксировать базовый heap snapshot

  2. Выполнить сценарий взаимодействия (загрузка сцены, переходы)

  3. Повторить snapshot

  4. Найти растущие классы Cesium:

    • Entity
    • Texture
    • Buffer
    • Cesium3DTileset
  5. Проверить retainers до глобальных объектов (window, viewer)

  6. Проверить наличие забытых event listeners


Поведенческие признаки утечек в CesiumJS

  • постепенное падение FPS при неизменной сцене
  • рост времени загрузки тайлов
  • увеличение пауз GC
  • фрагментация GPU памяти
  • зависание при переключении слоёв

Минимизация рисков накопления памяти

  • явное управление жизненным циклом всех объектов сцены
  • отказ от бесконтрольного создания entities
  • централизованное хранение ссылок на handlers и primitives
  • регулярная очистка кэшей при смене сцен
  • контроль количества одновременно активных tilesets