🧠 Утечки памяти в Unity 🧠
Ещё один полезный доклад с Unite 2025: How to find memory leaks with the Memory Profiler.
Несмотря на название, в докладе внимание уделено не только возможностям Memory Profiler.
Есть информация и про типы памяти: Native, Managed, Graphics, Untracked.
Есть про естественный рост потребления, про утечки памяти, про фрагментацию и её особенности с GC в Unity (т.е. про Boehm и CoreCLR).
В некотором смысле, он хорошо дополняет доклад Understanding Unity memory.
——————————
Основные причины утечек
• Частые аллокации. • Как следствие, фрагментация памяти. • Удержание ссылок на данные дольше, чем это необходимо, что мешает работе GC. • В частности, создание/загрузка ассетов без их последующего уничтожения.
——————————
Что к этому приводит
• Отсутствие своевременной очистки статических ссылок на объекты и коллекции.
• Отсутствие отписок для событий и делегатов.
• Использование анонимных функций и лямбда-выражений для подписки: ими невозможно отписаться без костылей и пристроек. И они могут вызывать при этом замыкание (про замыкание).
• Отсутствие вызова Dispose у того, что это требует.
• Неявное клонирование материалов: при обращении к .material может создаваться новый материал, который нужно уничтожать (нужно помнить про .sharedMaterial).
• Злоупотребление асинхронными методами без CancellationToken или в Forget-режиме: могут работать неконтролируемо долго и удерживать ссылки на данные.
——————————
Как этого избежать
• Сделал подписку — тут же задизайнил отписку. Сразу, не откладывая. Это и надёжнее (не забудешь), и получится архитектурно чище, т.к. придётся сразу продумать жизненный цикл реализуемой фичи.
• Аналогично для Dispose.
• Использовать симметрию: делать обратные действия в противоположных фазах. Это удобно дополняет прошлые пункты и упрощает контроль.
Например, в OnEnable — подписались, в OnDisable — отписались. В Start — инициализировали, в OnDestroy — деинициализировали. И т.д.
• Отказ от анонимных функций и лямбд для подписок: предпочитать методы.
• Избегать хранения данных в статике. Только в случаях реальной необходимости или соответствия дизайну приложения.
• Внимательно следить за статическими данными. И своевременно удалять оттуда неактуальные данные.
• Выгружать загруженные или созданные ранее ассеты, при потере необходимости в них.
• Использовать CancellationToken и/или принудительные таймауты для асинхронных методов.
• Использовать пул объектов вместо частого создания и уничтожения одних и тех же объектов для снижения фрагментации.
——————————
Ещё у меня есть короткие тематические нарезки из старых докладов: • Про фрагментацию • Про пул объектов