Статьи

Понимание сборки мусора в AS3

Вы когда-нибудь использовали приложение Flash и заметили отставание в нем? Все еще не знаете, почему эта крутая флеш-игра медленно запускается на вашем компьютере? Если вы хотите узнать больше о возможной причине этого, то эта статья для вас.

Мы нашли этого замечательного автора благодаря FlashGameLicense.com , месту, где можно покупать и продавать флэш-игры!

Переизданный учебник

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


Давайте посмотрим на конечный результат, к которому мы будем стремиться:


Прежде чем мы перейдем к реальной теме, вам сначала нужно немного узнать о том, как работает создание экземпляров и ссылок в AS3. Если вы уже читали об этом, я все же рекомендую прочитать этот маленький шаг. Таким образом, все знания будут свежими в вашей голове, и у вас не будет проблем с чтением остальной части этого Быстрого совета!

Создание и ссылка на экземпляры в AS3 отличается от большинства людей. Создание (или «создание») чего-либо происходит только тогда, когда код запрашивает создание объекта. Обычно это происходит через ключевое слово «new», но оно также присутствует, например, когда вы используете буквальный синтаксис или определяете параметры для функций. Примеры этого приведены ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
// Instantiation through the “new” keyword
new Object();
new Array();
new int();
new String();
new Boolean();
new Date();
 
// Instantiation through literal syntax
{};
[];
5
“Hello world!”
true
 
// Instantiation through function parameters
private function tutExample(parameter1:int, parameter2:Boolean):void

После того, как объект создан, он останется один до тех пор, пока что-то на него не ссылается Чтобы сделать это, вы обычно создаете переменную и передаете значение объекта в переменную, чтобы он знал, какой объект он в данный момент содержит. Однако (и это та часть, о которой большинство людей не знают), когда вы передаете значение переменной в другую переменную, вы не создаете новый объект. Вместо этого вы создаете еще одну ссылку на объект, который теперь содержит обе переменные! Смотрите изображение ниже для уточнения:

Изображение предполагает, что и Переменная 1, и Переменная 2 могут содержать смайлик (то есть они могут содержать один и тот же тип). В левой части существует только переменная 1 . Однако, когда мы создаем и устанавливаем для Переменной 2 одно и то же значение Переменной 1 , мы не создаем связь между Переменной 1 и Переменной 2 (верхняя правая часть изображения), вместо этого мы создаем связь между Смайликом и Переменной 2 (нижняя правая часть изображения).

Обладая этими знаниями, мы можем перейти к сборщику мусора.


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

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

Сборщик мусора – это форма управления памятью. Он направлен на устранение любого объекта, который не используется и занимает место в памяти системы. Таким образом, приложение может работать с минимальным использованием памяти. Давайте посмотрим, как это работает:

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

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


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

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

  • Уровень пакета и статические переменные.
  • Локальные переменные и переменные в объеме выполняемого метода или функции.
  • Переменные экземпляра из экземпляра основного класса приложения или из списка отображения.

Чтобы понять, как объекты обрабатываются сборщиком мусора, мы должны написать код и изучить, что происходит в файле примера. Я буду использовать проект FlashDevelop AS3 и компилятор Flex, но я предполагаю, что вы можете сделать это на любой IDE, так как мы не собираемся использовать конкретные вещи, которые существуют только в FlashDevelop. Я построил простой файл с кнопкой и текстовой структурой. Поскольку в этом кратком совете это не является целью, я быстро объясню это: при нажатии кнопки срабатывает функция. В любое время мы хотим отобразить некоторый текст на экране, вы вызываете функцию с текстом, и он отображается. Также есть другое текстовое поле для отображения описания кнопок.

Цель нашего файла примера – создать объекты, удалить их и изучить, что происходит с ними после их удаления. Нам понадобится способ узнать, является ли объект живым или нет, поэтому мы добавим слушатель ENTER_FRAME для каждого из объектов и заставим их отображать некоторый текст со временем, когда они были живы. Итак, давайте закодируем первый объект!

Я создал забавное изображение смайлика для объектов в честь великолепного учебного руководства по игре Avoider Майкла Джеймса Уильямса, в котором также используются изображения смайликов. Каждый объект будет иметь номер на голове, чтобы мы могли его идентифицировать. Также я назвал первый объект TheObject1 , а второй объект TheObject2 , поэтому его будет легко отличить. Давайте перейдем к коду:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
private var _theObject1:TheObject1;
 
private function newObjectSimple1(e:MouseEvent):void
{
    // If there is already an object created, do nothing
    if (_theObject1)
        return;
     
    // Create the new object, set it to the position it should be in and add to the display list so we can see it was created
    _theObject1 = new TheObject1();
    _theObject1.x = 320;
    _theObject1.y = 280;
    _theObject1.addEventListener(Event.ENTER_FRAME, changeTextField1);
     
    addChild(_theObject1);
}

Второй объект выглядит почти так же. Вот:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
private var _theObject2:TheObject2;
 
private function newObjectSimple2(e:MouseEvent):void
{
    // If there is already an object created, do nothing
    if (_theObject2)
        return;
     
    // Create the new object, set it to the position it should be in and add to the display list so we can see it was created
    _theObject2 = new TheObject2();
    _theObject2.x = 400;
    _theObject2.y = 280;
    _theObject2.addEventListener(Event.ENTER_FRAME, changeTextField2);
     
    addChild(_theObject2);
}

В коде newObjectSimple1 () и newObjectSimple2 () являются функциями, которые запускаются при нажатии на соответствующую кнопку. Эти функции просто создают объект и добавляют его на экран дисплея, поэтому мы знаем, что он был создан. Кроме того, он создает приемник событий ENTER_FRAME в каждом объекте, который заставляет их отображать сообщение каждую секунду, пока они активны. Вот функции:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
private function changeTextField1(e:Event):void
{
    // Our example is running at 30FPS, so let’s add 1/30 on every frame in the count.
    _objectCount1 += 0.034;
     
    // Checks to see if _objectCount1 has passed one more second
    if(int(_objectCount1) > _secondCount1)
    {
        // Displays a text in the screen
        displayText(“Object 1 is alive… ” + int(_objectCount1));
         
        _secondCount1 = int(_objectCount1);
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
private function changeTextField2(e:Event):void
{
    // Our example is running at 30FPS, so let’s add 1/30 on every frame in the count.
    _objectCount2 += 0.034;
     
    // Checks to see if _objectCount2 has passed one more second
    if(int(_objectCount2) > _secondCount2)
    {
        // Displays a text in the screen
        displayText(“Object 2 is alive… ” + int(_objectCount2));
         
        _secondCount2 = int(_objectCount2);
    }
}

Эти функции просто отображают сообщение на экране со временем, когда объекты были живы. Вот SWF-файл с текущим примером:


Теперь, когда мы рассмотрели создание объектов, давайте попробуем кое-что: задумывались ли вы, что произойдет, если вы действительно удалите (удалите все ссылки) объект? Собирается ли мусор? Это то, что мы сейчас проверим. Мы собираемся создать две кнопки удаления, по одной для каждого объекта. Давайте сделаем код для них:

01
02
03
04
05
06
07
08
09
10
11
12
private function deleteObject1(e:MouseEvent):void
{
    // Check if _theObject1 really exists before removing it from the display list
    if (_theObject1 && contains(_theObject1))
        removeChild(_theObject1);
     
    // Removing all the references to the object (this is the only reference)
    _theObject1 = null;
     
    // Displays a text in the screen
    displayText(“Deleted object 1 successfully!”);
}
01
02
03
04
05
06
07
08
09
10
11
12
private function deleteObject2(e:MouseEvent):void
{
    // Check if _theObject2 really exists before removing it from the display list
    if (_theObject1 && contains(_theObject2))
        removeChild(_theObject2);
     
    // Removing all the references to the object (this is the only reference)
    _theObject2 = null;
     
    // Displays a text in the screen
    displayText(“Deleted object 2 successfully!”);
}

Давайте посмотрим на SWF сейчас. Как ты думаешь, что произойдет?

Как вы видете. Если вы нажмете «Создать объект1», а затем «Удалить объект1», на самом деле ничего не произойдет! Мы можем сказать, что код работает, потому что текст появляется на экране, но почему объект не удаляется? Объект все еще там, потому что он на самом деле не был удален. Когда мы очистили все ссылки на него, мы сказали коду сделать его пригодным для сбора мусора, но сборщик мусора никогда не запускается. Помните, что сборщик мусора будет работать только тогда, когда текущее использование памяти приблизится к запрошенной памяти, когда приложение начнет работать. Это имеет смысл, но как мы собираемся это проверить?

Я, конечно, не собираюсь писать кусок кода, чтобы заполнить наше приложение бесполезными объектами, пока использование памяти не станет слишком большим. Вместо этого мы будем использовать функцию, в настоящее время не поддерживаемую Adobe, согласно статье Гранта Скиннера , которая заставляет запускать сборщик мусора. Таким образом, мы можем запустить этот простой метод и посмотреть, что произойдет при его запуске. Кроме того, теперь для простоты я буду называть сборщик мусора GC. Вот функция:

01
02
03
04
05
06
07
08
09
10
11
12
private function forceGC(e:MouseEvent):void
{
    try
    {
        new LocalConnection().connect(‘foo’);
        new LocalConnection().connect(‘foo’);
    }
    catch (e:*) { }
     
    // Displays a text in the screen
    displayText(“—– Garbage collection triggered —–“);
}

Известно, что эта простая функция, которая создает только два объекта LocalConnection (), заставляет GC запускаться, поэтому мы будем вызывать ее, когда хотим, чтобы это произошло. Я не рекомендую использовать эту функцию в серьезном приложении. Если вы делаете это для тестирования, реальных проблем не возникает, но если это приложение, которое будет распространяться среди людей, это не очень хорошая функция, так как она может привести к негативным последствиям.

Что я рекомендую для таких случаев, так это то, что вы просто позволяете GC работать в своем темпе. Не пытайтесь форсировать это. Вместо этого сосредоточьтесь на эффективном кодировании, чтобы не возникало проблем с памятью (мы рассмотрим это на шаге 6). Теперь давайте снова посмотрим на наш пример SWF и нажмите кнопку «Собрать мусор» после создания и удаления объекта.

Вы проверяли файл? Это сработало! Вы можете видеть, что теперь, после удаления объекта и запуска ГХ, он удаляет объект! Обратите внимание, что если вы не удалите объект и не вызовете GC, ничего не произойдет, поскольку в коде все еще есть ссылка на этот объект. А что если мы попытаемся сохранить две ссылки на объект и удалить одну из них?


Теперь, когда мы доказали, что GC работает именно так, как мы хотели, давайте попробуем что-то еще: свяжем другую ссылку на объект (Object1) и удалим оригинал. Во-первых, мы должны создать функцию для связи и отмены ссылки на наш объект. Давай сделаем это:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private function saveObject1(e:MouseEvent):void
{
    // _onSave is a Boolean to check if we should link or unlink the reference
    if (_onSave)
    {
        // If there is no object to save, do nothing
        if (!_theObject1)
        {
            // Displays a text in the screen
            displayText(“There is no object 1 to save!”);
             
            return;
        }
         
        // A new variable to hold another reference to Object1
        _theSavedObject = _theObject1;
         
        // Displays a text in the screen
        displayText(“Saved object 1 successfully!”);
         
        // On the next time this function runs, unlink it, since we just linked
        _onSave = false;
    }
    else
    {
        // Removing the reference to it
        _theSavedObject = null;
         
        // Displays a text in the screen
        displayText(“Unsaved object 1 successfully!”);
         
        // On the next time this function runs, link it, since we just unlinked
        _onSave = true;
    }
}

Если мы сейчас протестируем наш swf, мы заметим, что если мы создадим Object1, затем сохраним его, удалим и заставим GC работать, ничего не произойдет. Это потому, что теперь, даже если мы удалили «оригинальную» ссылку на объект, есть еще одна ссылка на него, которая не дает ему возможность собирать мусор. Это в основном все, что вам нужно знать о сборщике мусора. В конце концов, это не загадка. но как мы применим это к нашей нынешней среде? Как мы можем использовать эти знания для предотвращения медленного запуска нашего приложения? Это то, что Шаг 6 покажет нам: как применить это в реальных примерах.


Теперь самое интересное: заставить ваш код работать с GC эффективно! Этот шаг предоставит полезную информацию, которую вы должны хранить всю свою жизнь – сохраните ее правильно! Во-первых, я хотел бы представить новый способ создания ваших объектов в вашем приложении. Это простой, но эффективный способ сотрудничества с GC. Этот способ вводит два простых класса, которые могут быть расширены для других, как только вы поймете, что он делает.

Идея этого способа состоит в том, чтобы реализовать функцию – называемую destroy () – на каждом объекте, который вы создаете, и вызывать ее каждый раз, когда вы заканчиваете работать с объектом. Функция содержит весь код, необходимый для удаления всех ссылок на объект и из него (за исключением ссылки, которая использовалась для вызова функции), поэтому вы убедитесь, что объект покидает ваше приложение полностью изолированным и легко распознается GC. Причина этого объясняется на следующем шаге. Давайте посмотрим на общий код функции:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
// Create this in every object you use
public function destroy():void
{
    // Remove event listeners
    // Remove anything in the display list
    // Clear the references to other objects, so it gets totally isolated
     
}
 
// …
// When you want to remove the object, do this:
theObject.destroy();
 
// And then null the last reference to it
theObject = null;

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

  • Объекты, которые используются только в интервале выполнения : будьте осторожны с этими объектами , так как они могут быть теми, которые занимают много памяти. Эти объекты существуют только в течение некоторого периода времени (например, для хранения значений при выполнении функции), и к ним обращаются не очень часто. Не забудьте удалить все ссылки на них после того, как вы закончите с ними, в противном случае вы можете иметь многие из них в своем приложении, только занимая место в памяти. Имейте в виду, что если вы создаете много ссылок на них, вы должны исключить каждую из них с помощью функции destroy () .
  • Объекты, оставленные в списке отображения : всегда удаляйте объект из списка отображения, если хотите его удалить. Список отображения является одним из корней сборки мусора (помните?), И поэтому очень важно, чтобы при удалении от него не было объектов.
  • Сценарные, родительские и корневые ссылки : если вам нравится много использовать эти свойства, не забудьте удалить их, когда закончите. Если у многих ваших объектов есть ссылки на них, у вас могут быть проблемы!
  • Слушатели событий : иногда ссылка, которая удерживает ваши объекты от сбора, является слушателем событий. Не забудьте удалить их или использовать их как слабых слушателей, если это необходимо.
  • Массивы и векторы : иногда ваши массивы и векторы могут содержать другие объекты, оставляя в них ссылки, о которых вы можете не знать. Будьте осторожны с массивами и векторами!

Хотя работа с GC великолепна, она не идеальна. Вы должны обратить внимание на то, что вы делаете, иначе с вашим приложением могут случиться плохие вещи. Я хотел бы продемонстрировать проблему, которая может возникнуть, если вы не выполните все необходимые шаги для правильной работы кода с GC.

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

Когда GC запускается, он выполняет две простые задачи, чтобы проверить объекты для удаления. Одной из этих задач является подсчет количества ссылок на каждый объект. Все объекты с 0 ссылками собираются одновременно. Другая задача – проверить, существует ли какая-то небольшая группа объектов, которые связаны друг с другом, но к ним нет доступа, что приводит к потере памяти. Проверьте изображение:

Как вы можете видеть, зеленые объекты не могут быть достигнуты, но их счетчик ссылок равен 1. GC выполняет вторую задачу, чтобы проверить этот кусок объектов и удаляет все из них. Однако, когда кусок слишком велик, сборщик мусора «отказывается» от проверки и предполагает, что объекты могут быть достигнуты. Теперь представьте, если у вас есть что-то подобное:

Это остров ссылок. Это заняло бы много памяти из системы и не было бы собрано GC из-за сложности этого. Звучит довольно плохо, а? Этого можно легко избежать, хотя. Просто убедитесь, что вы очистили все ссылки на объект и с него, и тогда таких страшных вещей не произойдет!


Это пока что. В этом кратком совете мы узнали, что мы можем сделать наш код лучше и эффективнее, чтобы уменьшить задержки и проблемы с памятью, что сделает его более стабильным. Чтобы сделать это, мы должны понять, как ссылки на объекты работают в AS3, и как извлечь из них выгоду для правильной работы GC в нашем приложении. Несмотря на то, что мы можем сделать наше приложение лучше, мы должны быть осторожны при этом, иначе оно может стать еще более запутанным и медленным!

Надеюсь, вам понравился этот простой совет. Если у вас есть какие-либо вопросы, оставьте комментарий ниже!