Почему уменьшение числа сильных ссылок до нуля не гарантирует немедленного уменьшения RSS-памяти процесса?
ARC отвечает за время жизни объектов, но не управляет напрямую размером RSS-памяти процесса. Когда последняя сильная ссылка исчезает, объект обычно деинициализируется и его память становится доступной аллокатору, однако аллокатор может сохранить освобождённые страницы для повторного использования, поэтому RSS не обязан сразу уменьшиться.
ARC появился как способ автоматизировать ручное управление подсчёком ссылок. Он устраняет необходимость явно расставлять операции удержания и освобождения объектов, снижая риск утечек из-за пропущенного освобождения и аварий из-за преждевременного освобождения.
При этом ARC решает задачу управления временем жизни объектов, а не задачу возврата виртуальной памяти операционной системе. За размещение и освобождение блоков памяти отвечают рантайм и системный аллокатор.
Разработчик видит, что после исчезновения последних ссылок вызывается deinit, но RSS приложения остаётся высоким. Ошибочно сделать вывод, что объект не освободился или ARC работает неправильно.
Фактически нужно различать три состояния: объект больше не жив, его память возвращена аллокатору и страницы памяти возвращены операционной системе. Эти события не обязаны совпадать по времени, а последнее может не произойти вовсе, пока процессу выгодно повторно использовать уже выделенные страницы.
При обнулении последней сильной ссылки ARC запускает уничтожение экземпляра: вызывается deinit, после чего занимаемый объектом блок становится свободным с точки зрения аллокатора. Это означает, что память может быть использована для следующих выделений внутри процесса.
RSS показывает объём резидентных страниц, связанных с процессом, а не количество живых Swift-объектов. Аллокатор может удерживать свободные блоки и страницы из-за оптимизации будущих выделений, особенностей размеров блоков, фрагментации или требований к объединению страниц.
Поэтому вызов deinit подтверждает завершение жизни конкретного экземпляра, но не гарантирует уменьшение RSS. Обратная ситуация также возможна: RSS изменился из-за работы аллокатора, хотя количество живых объектов почти не изменилось.
При диагностике нужно проверять несколько независимых признаков:
deinit у ожидаемых объектов;autoreleasepool;Главный компромисс таков: удержание освобождённых страниц может временно увеличивать RSS, но ускоряет последующие выделения и уменьшает количество обращений к операционной системе. Принудительно добиваться снижения RSS после каждого освобождения обычно не нужно и может ухудшить производительность.
Экран последовательно загружает крупные изображения. После закрытия каждого экрана экземпляры моделей и изображений вызывают deinit, но RSS приложения постепенно растёт и затем стабилизируется на высоком уровне.
Вариант с немедленным поиском цикла ссылок полезен, если deinit не вызывается. Но если deinit стабильно вызывается, этот вариант не объясняет проблему. Вариант с принудительным уменьшением памяти процесса может быть недоступен, нестабилен или ухудшить производительность.
Правильное решение — проверить граф живых объектов и профилировщик памяти, отдельно исследовать кэши, временные Objective-C-объекты и крупные буферы, а затем повторить сценарий после освобождения ресурсов. Если число живых объектов не растёт, а память переиспользуется при следующих загрузках, высокий RSS объясняется поведением аллокатора, а не утечкой ARC.
1. Означает ли вызов deinit, что память уже возвращена операционной системе?
Нет. deinit сообщает об окончании пользовательской фазы жизни экземпляра и освобождении его ресурсов. Сам блок памяти после этого может оставаться во внутренних структурах аллокатора и быть возвращён операционной системе только при выполнении дополнительных условий.
2. Можно ли доказать утечку ARC по одному росту RSS?
Нет. RSS включает страницы, занятые не только живыми Swift-объектами, но и аллокатором, библиотеками, буферами, кэшами и временными объектами. Для вывода об утечке нужно наблюдать устойчивый рост числа живых объектов или удерживаемых блоков, отсутствие ожидаемых deinit и конкретный путь удержания.
3. Как отличить цикл сильных ссылок от памяти, удерживаемой аллокатором?
При цикле объекты обычно не вызывают deinit, потому что их сильные ссылки продолжают поддерживать друг друга. При удержании памяти аллокатором deinit вызывается, число живых экземпляров не растёт, а последующие выделения могут использовать уже занятую RSS-память без пропорционального увеличения процесса.