Утечки памяти

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

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

Просто о сложном

Для начала надо усвоить две важные вещи:

  • Примитивные типы, такие как uint, int, Boolean, String, Number всегда передаются только в качестве значений
  • Сложные типы, такие как Array, XMLList, XML, Date и т.д. передаются всегда в качестве ссылки на объект

Что это значит на практике? Давайте рассмотрим примеры:

Результат:

52
42

Как видите при изменении value1 значение value2 осталось прежним, так как в value2 передается именно значение из value1.

Другой пример:

Результат:

5,2,3,4
5,2,3,4

На этот раз дело обстоит иначе, если в предыдущем примере значения у нас разнились, то в текущем случае массив value2 “принял” изменения массива value1.

Почему так произошло? Дело в том, что Array (массив) это сложный тип, и когда мы присваиваем переменной value2 значение value1, то на самом деле мы передаем ссылку на уже существующий массив. То есть в value1 и в value2 находится ссылка на один и тот же объект, который размещен в памяти нашего приложения.

Повторим: при создании массива (как и любой другой сущности сложного типа) мы создаем объект в памяти приложения, а в качестве значения переменной получаем ссылку на него.

Утечки памяти

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

Причины:

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

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

Среда выполнения имеет встроенный механизм очистки памяти под названием Garbage Collector (Сборщик Мусора). Именно этот механизм очищает память после того как все ссылки на объект были уничтожены (приравнены к null), но делает это не сразу, а только при необходимости освободить память, и именно в тот момент когда новый объект создается, а не удаляется старый. В целом же поведение сборщика мусора не является очевидным и планируемым.

Почему ссылки на объекты могут сохраняться?

Присвоение переменной значения null не гарантируется удаление его из памяти. На это есть несколько причин:

  • К данном объекту привязаны слушатели событий, и перед тем как присваивать ему значение null необходимо отписаться от всех событий, с помощью метода removeEventListener.
  • Если это экранный объект, то он может находится в дисплей-листе другого экранного объекта. Следовательно его необходимо сначала удалить из дисплей-листа, с помощью метода removeChild
  • Работающие таймеры (класс Timer) всегда остаются в памяти, даже если у них отсутствуют слушатели. Необходимо останавливать таймеры перед удалением, с помощью метода stop.
Деактивация объектов

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

  • В случае когда вы работаете с BitmapData, то при её создании резервируется количество байт = ширина * высоту * 4 (width * height * 4), где 4 это количество байт на один пиксель.
    Например: 0xFFFFFFFF представляет собой белый непрозрачный цвет в формате ARGB, 0xAARRGGBB.

    Чтобы очистить память используемую BitmapData необходимо сначала использовать метод dispose. После этого можно удалять ссылки на данный объект.

  • Многие разработчики очень любят использовать XML в качестве хранилища данных и конфигурационных структур. Структура XML занимает немалое количество памяти при большом объеме содержащейся в ней информации.

    Начиная с версии 10.1 Flash Player была добавлена возможность сделать XML доступным для сбора в качестве мусора с помощью статического метода disposeXML класса System.

    Я крайне рекомендую уничтожать уже ненужные объекты XML, после их использования.

2. Чрезмерное создание объектов

Как понять чрезмерно часто вы создаете объекты или нет? Давайте рассмотрим на простом примере, мы будем рисовать квадраты 15×15 через каждые 20 пикселей в нашей bitmapData

Что не так в этом примере? Каждую итерацию цикла мы создаем новый объект Rectangle, вместо того, чтобы создать всего лишь один, и менять его координаты, что может приводить к проблемам с производительностью приложения. Давайте исправим.

3. Повторное создание экземпляра BitmapData вместо повторного использования

В версии Flash Player 10.1 была введена прекрасная возможность использовать повторно существующий BitmapData одновременно для нескольких экранных объектов Bitmap.
Например, у вас есть изображение со снежинкой и вы внедрили его с помощью мета-тэга [Embed] или в графическую библиотеку.

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

Таким образом у нас будет задействована всего лишь одна BitmapData, но на экране мы увидим 200 одинаковых снежинок.

4. Отсутствие пула объектов

Для начала нужно понять что такое пул объектов и где его применять.
Пул представляет собой хранилище уже проинициализированных объектов, которые будут использоваться в рабочем процессе. В случае когда возникает необходимость в объекте, он не будет создаваться, а будет взят из пула, и после использования будет возвращен обратно.

Использовать пул необходимо в местах, где часто создаются и удаляются объекты, чтобы не провоцировать процесс сбора мусора. Каждая итерация сбора мусора может затребовать использования большого количества ресурсов при достаточной сложности объектов, что неизбежно приводит к “торможению” приложений.

Реализация пула может выглядеть совершенно по-разному, это может быть отдельный класс выполняющий роль пула для любых объектов определенного типа, или же типизированный Vector, или просто Array.

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

5. Использование событий там, где это неуместно

Классическим примером может служит диспетчеризация событий во время цикла Event.ENTER_FRAME. Вы подписались на ENTER_FRAME и испускаете собственные события на каждом или на определенном кадре, по той или иной причине (например вам нужно отследить когда MovieClip достигнет определенного кадра).

В этом случае лучше воздержаться от диспетчеризации использования событий, а использовать обратные вызовы – колбеки (callback).
При использовании колбеков вы не создаете дополнительных объектов событий, которые хранятся в памяти, что может вызвать падение производительности. Серьёзное падение производительности
может быть достигнуто при работе с экранными объектами, когда диспетчеризация происходит с использованием фаз захвата (capture phase) и цепочками диспетчеризации (events bubbling).

6. Векторная и растровая графика

Векторная графика в сравнении с растровой в большей степени экономит память приложения. Но это палка о двух концах – использование большого количества векторной графики неизбежно приводит к снижению производительности, так как для её отображения будет задействовано больше ресурсов CPU и GPU, чем для растровой графики. Растр же в свою очередь требует значительно меньше ресурсов, но больше памяти.

Разработчику нужно помнить о сохранении баланса в использовании растра и вектора.

Для мобильных решений же справедливо использование только растровой графики. Ввиду слабой производительности процессоров на мобильных устройствах – векторный контент может отображаться с достаточно низким fps.

Профилактика

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

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

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

Я не призываю называть метод обязательно destroy, это может быть и dispose и что угодно, сигнализирующее о том, что данный метод “очищает” объект.

Итоги

  1. Отписывайтесь от событий которые больше вам не пригодятся
  2. Уничтожайте все неиспользуемые данные
  3. Помечайте ссылки на неиспользуемые объекты при помощи null, для того, чтобы сборщик мусора мог их уничтожить
  4. Не создавайте множество объектов там, где можно обойтись одним
  5. Используйте пулы вместо частого создания и удаления объектов
  6. Возьмите за привычку писать деструкторы
  7. Пишите понятный код :-)

One Comment

  • Женя Филиппов |

    Чётко, действительно чётко.