Flash Player: получить счетчик ссылок для переменной - PullRequest
2 голосов
/ 30 сентября 2010

Я собираюсь создать библиотеку, которая должна быть очень осторожна с управлением памятью.По сути, у меня есть для создания статической фабрики, чтобы «разогнать» экземпляры моего инструмента по запрашиваемым объектам.(У меня нет выбора в этом вопросе, мне действительно нужно использовать синглтон) Мы назовем этот класс FooFactory.FooFactory определяет один метод, getFoo(key:String):Foo.

getFoo ищет в частном статическом flash.utils.Dictionary объекте соответствующий экземпляр Foo и либо лениво-создает его, либо просто возвращает.В любом случае FooFactory ДОЛЖЕН сохранять ссылку на каждый созданный экземпляр Foo, поэтому все экземпляры Foo могут быть обновлены с помощью FooFactory с использованием метода, называемого updateFoos():void.

Вот некоторый псевдокод того, о чем я говорю:

public class FooFactory {
    private static const foos:Dictionary = new Dictionary(true); //use weak keys for gc

    public static function getFoo(key:String):Foo {
        //search for the specified instance in the 'foos' dictionary
        if (foos[key] != null && foos[key] != undefined) {
            return foos[key];
        } else {
            //create foo if it doesn't exist. 
            var foo:Foo = new Foo(key);
            foos[key] = foo;
            return foo;
        }
    }

    public static function updateFoos():void {
        for (var key:String in foos) {
            if (foos[key] != null && foos[key] != undefined) {
                Foo(foos[key]).dispatchEvent(new Event("update"));
            }
        }
    }
}

Реальная функция и идентификатор Foo не так уж важны.

Что IS важно, так это сборка мусора в этой ситуации.В прошлом я создал нечто похожее на приведенный выше пример, и у меня были невероятные проблемы со сборкой мусора.(Я действительно использовал массив, а не словарь, что может быть частью проблемы.) Что произойдет, так это то, что в моем приложении Flex модули никогда не будут выгружаться, поскольку экземпляры имеют ссылку на Foo экземпляр, на который ссылается FooFactory, вот так: (опять же, псевдокод)

<?xml version="1.0"?>
<s:Group>
    <fx:Script>
        <![CDATA[
            private static const foo:Foo = FooFactory.getFoo('myfoo');
        ]]>
    </fx:Script>
</s:Group>

То, что я хочу знать, это две следующие вещи:

  1. Isпсевдокод выше "сборщик мусора безопасен?"IE: Будут ли мои модули выгружаться правильно, и будут ли сборщики мусора экземпляров Group выше?
  2. Есть ли способ во Flash Player (даже в проигрывателе отладки, если это необходимо), который может помочь мне в подсчете ссылок, чтобы я мог проверить, собирается ли что-то мусор или нет?

Мне известен API flash.sampler, но я не уверен, как его использовать для подсчета ссылок.

Ответы [ 3 ]

1 голос
/ 30 сентября 2010

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

private static const foo:Foo = FooFactory.getFoo('myfoo');

Здесь ваш модуль имеет ссылку на экземпляр Foo.Это означает, что этот экземпляр Foo не будет доступен для сбора, пока ваш модуль не может быть собран.Модуль имеет ссылку на foo, поэтому здесь foo достижимо (если модуль достижим).Это не так, наоборот.Даже если foo живет вечно, у него нет ссылки на модуль, поэтому он не будет записывать его.

Конечно, могут быть и другие вещи, предотвращающие сбор вашего модуля, но foo здесь не является виновником, , если foo не получит ссылку на модуль каким-либо образом,Например, модуль добавляет прослушиватель к foo, что в данном случае аналогично записи:

foo.addReference(this); // where this is your module

Тот факт, что вы объявляете экземпляр как const, не должен ничего менятьSE, либо.Это только означает, что сохраненная ссылка не может быть изменена позднее.Однако, если вы хотите обнулить foo на более позднем этапе, вы не сможете этого сделать, потому что это приведет к переназначению ссылки;и вы не можете переназначить ссылку const (вы должны получить ошибку компилятора).Теперь это связывает foo с модулем.Пока ваш модуль жив, он будет иметь ссылку на foo, поэтому foo не будет собираться.

Относительно этой строки:

private static const foos:Dictionary = new Dictionary(true); //use weak keys for gc

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

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

Что касается вашего второго вопроса, я бы порекомендовал FlexBuilder /Профилировщик FlashBuilder, если вам доступен FB.Это не самый интуитивно понятный инструмент, но с некоторой практикой он может быть действительно полезен для отслеживания проблем с памятью.По сути, он даст вам знать, сколько экземпляров данного класса было создано, сколько из них еще живы, какие объекты имеют ссылки на эти экземпляры и где были выделены все эти объекты (опция не проверяется по умолчанию при запускепрофилировщик, купить очень удобно, чтобы отследить утечку).

PS

Относительно вашего комментария:

Возможно, реальная проблема - статическаяконстантная ссылка связана с экземпляром группы?Если это проблема, я мог бы просто абстрагировать Foo от интерфейса, а затем создать нечто под названием FooWeakReference, в котором использовался бы слабый словарь для ссылки на фактический объект Foo.Мысли?

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

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

Кроме того, слабые реферы открывают совершенно новый тип ошибок: что произойдет, если тот экземпляр, который вы создали, лениво исчезнет, ​​прежде чем вы сможете его использовать, или еще хуже, пока вы его используете?Прослушиватели событий, которые перестают работать при недетерминированно воспроизводимых обстоятельствах (например, вы добавили прослушиватель к объекту, который пропал), возможные нулевые ссылки (например, вы пытаетесь добавить прослушиватель к объекту, который больше не существует) и т. Д. И т. Д. И т. Д.не пей слабую справочную kool-aid;).

Addedum

В заключение, как последний вопрос, верно ли для меня сказать, чтоAS3 решение не существует для подсчета ссылок?Я создаю полный пакет модульного тестирования для этой библиотеки, и если бы я мог сделать что-то вроде Assert.assertEquals (0, getReferenceCount (foo)), это было бы рад.

Ну да.Вы не можете получить счетчик ссылок на данный объект из Actionscript.Даже если бы это было возможно, я не уверен, что это помогло бы, потому что подсчет ссылок - только часть того, как работает GC.Другой - алгоритм метки и развертки.Таким образом, если объект с нулевым реф-счетом является собираемым, но он может иметь, скажем, 3 ссылки и при этом быть собираемым.Чтобы действительно определить, является ли объект коллекционируемым или нет, вы должны быть в состоянии подключиться к процедуре GC, я полагаю, и это невозможно из AS.

Кроме того, этот код никогда не будет работать.

Assert.assertEquals(0, getReferenceCount(foo)

Почему?Здесь вы пытаетесь запросить некоторый API, чтобы узнать, является ли объект коллекционным или нет.Поскольку вы не можете этого знать, давайте предположим, что это говорит вам, был ли объект собран или нет.Проблема в том, что foo в этот момент либо ноль, либо не ноль.Если это null, это недействительная ссылка, поэтому по понятным причинам вы не сможете получить из нее полезную информацию.Если это не нуль, это действительная ссылка на объект, тогда вы можете получить к нему доступ, и он жив;так что вы уже знаете ответ на вопрос, который задаете.

Теперь, я думаю, я понимаю вашу цель.Вы хотите иметь возможность программно сказать, просочились ли у вас определенные объекты.До некоторой степени это возможно.Он включает в себя использование API flash.sampler, как вы упомянули в своем первоначальном вопросе.

Я предлагаю вам проверить Flash Preload Profiler от jpauclair:

I haven 'Я не использовал его, но похоже, что он может быть так же хорош, как и FB-профилировщик для наблюдения за памятью.

Так как это код Actionscript (и так как он с открытым исходным кодом), вы можете использовать его по своему желанию,Я просто просмотрел код, но мне удалось получить очень простое доказательство концепции с помощью monkey-patching SampleAnalyzer класс:

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

Итак, я написал простой класс, который будет запускать этот профилировщик.Идея состоит в том, что когда вы создаете объект, вы можете попросить этот класс просмотреть его.Идентификатор распределения этих объектов будет просматриваться в таблице выделенных объектов, поддерживаемой профилировщиком памяти, и его дескриптор будет храниться локально (только идентификатор) .Эта ручка id также будет возвращена для удобства.Таким образом, вы можете сохранить этот дескриптор id и позже использовать его, чтобы проверить, был ли объект собран или нет.Также есть метод, который возвращает список всех дескрипторов, которые вы добавили, и другой, который возвращает список добавленных дескрипторов, которые указывают на живые объекты.Дескриптор позволит вам получить доступ к исходному объекту (если он еще не был собран), его классу и трассировке стека выделения.(Я не храню сам объект или объект NewObjectSample, чтобы избежать его случайного закрепления)

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

Итак, вот код ProfileRunner с некоторыми комментариями.

import flash.sampler.Sample;
import flash.sampler.NewObjectSample;
import flash.utils.Dictionary;

class ProfilerRunner {

    private var _watched:Array;

    public function ProfilerRunner() {
        _watched = [];
    }

    public function init():void {
        // setup the analyzer. I just copied this almost verbatim 
        // from SamplerProfiler... 
        // https://code.google.com/p/flashpreloadprofiler/source/browse/trunk/src/SamplerProfiler.as
        SampleAnalyzer.GetInstance().ResetStats();
        SampleAnalyzer.GetInstance().ObjectStatsEnabled = true;
        SampleAnalyzer.GetInstance().InternalEventStatsEnabled = false;         
        SampleAnalyzer.GetInstance().StartSampling();       
    }

    public function destroy():void {
        _watched = null;
    }

    private function updateSampling(hook:Function = null):void {
        SampleAnalyzer.GetInstance().PauseSampling();
        SampleAnalyzer.GetInstance().ProcessSampling();     

        if(hook is Function) {
            var samples:Dictionary = SampleAnalyzer.GetInstance().GetRawSamplesDict();
            hook(samples);
        }

        SampleAnalyzer.GetInstance().ClearSamples();
        SampleAnalyzer.GetInstance().ResumeSampling();          

    }

    public function addWatch(object:Object):WatchHandle {
        var handle:WatchHandle;
        updateSampling(function(samples:Dictionary):void {
            for each(var sample:Sample in samples) {
                var newSample:NewObjectSample;
                if((newSample = sample as NewObjectSample) != null) {
                    if(newSample.object == object) {
                        handle = new WatchHandle(newSample);
                        _watched.push(handle);
                    }
                }
            }           
        });
        return handle;
    }

    public function isActive(handle:WatchHandle):Boolean {
        var ret:Boolean;
        updateSampling(function(samples:Dictionary):void{
            for each(var sample:Sample in samples) {
                var newSample:NewObjectSample;
                if((newSample = sample as NewObjectSample) != null) {
                    if(newSample.id == handle.id) {
                        ret = true;
                        break;
                    }
                }
            }                   
        });
        return ret;
    }

    public function getActiveWatchedObjects():Array {
        var list:Array = [];
        updateSampling(function(samples:Dictionary):void {
            for each(var handle:WatchHandle in _watched) {
                if(samples[handle.id]) {
                    list.push(handle);
                }
            }               
        });
        return list;
    }

    public function getWatchedObjects():Array {
        var list:Array = [];
        for each(var handle:WatchHandle in _watched) {
            list.push(handle);
        }               
        return list;        
    }


}

class WatchHandle {

    private var _id:int;
    private var _objectProxy:Dictionary;
    private var _type:Class;
    private var _stack:Array;

    public function get id():int {
        return _id;
    }

    public function get object():Object {
        for(var k:Object in _objectProxy) {
            return k;
        }
        return null;
    }

    public function get stack():Array {
        return _stack;
    }

    public function getFormattedStack():String {
        return "\t" + _stack.join("\n\t");
    }
    public function WatchHandle(sample:NewObjectSample) {
        _id = sample.id;
        _objectProxy = new Dictionary(true);
        _objectProxy[sample.object] = true;
        _type = sample.type;
        _stack = sample.stack;
    }

    public function toString():String {
        return "[WatchHandle id: " + _id + ", type: " + _type + ", object: " + object + "]";
    }
}

А вот простая демонстрация того, как вы бы это использовали.

Он инициализирует бегуна, выделяет 2 объекта Foo, а затем, через 2 секунды, завершает сам. Обратите внимание, что в финализаторе я обнуляю один из объектов Foo и завершаю профилировщик. Там я пытаюсь заставить GC подождать некоторое время (GC не является синхронным), а затем проверить, живы ли эти объекты. Первый объект должен вернуть false, а второй true. Итак, это то место, где вы бы заявили о себе. Имейте в виду, что все это будет работать только в отладочном плеере.

Итак, без каких-либо дополнительных дополнений, вот пример кода:

package {

    import flash.display.Sprite;
    import flash.sampler.NewObjectSample;
    import flash.sampler.Sample;
    import flash.system.System;
    import flash.utils.Dictionary;
    import flash.utils.setTimeout;

    public class test extends Sprite
    {

        private var x1:Foo;
        private var x2:Foo;

        private var _profiler:ProfilerRunner;

        private var _watch_x1:WatchHandle;
        private var _watch_x2:WatchHandle;

        public function test()
        {
            init();
            createObjects();
            setTimeout(finalize,2000);
        }

        public function init():void {
            initProfiler();
        }

        public function finalize():void {
            x1 = null;
            finalizeProfiler(); 
        }   

        private function initProfiler():void {
            _profiler = new ProfilerRunner();
            _profiler.init();
        }

        private function finalizeProfiler():void {
            //  sometimes, calling System.gc() in one frame doesn't work
            //  you have to call it repeatedly. This is a kind of lame workaround
            //  this should probably be hidden in the profiler runner
            var count:int = 0;
            var id:int = setInterval(function():void {
                System.gc();                
                count++;
                if(count >= 3) {
                    clearInterval(id);
                    destroyProfiler();
                }
            },100);
        }

        private function destroyProfiler():void {
            //  boolean check through saved handles
            trace(_profiler.isActive(_watch_x1));
            trace(_profiler.isActive(_watch_x2));
            //  print all objects being watched
            trace(_profiler.getWatchedObjects());   
            //  get a list of the active objects and print them, plus the alloc stack trace    
            var activeObjs:Array = _profiler.getActiveWatchedObjects();
            for each(var handle:WatchHandle in activeObjs) {
                trace(handle);
                trace(handle.getFormattedStack());
            }
            _profiler.destroy();

        }               

        private function createObjects():void {

            x1 = new Foo();
            x2 = new Foo();
                    // add them for watch. Also, let's keep a "handle" to
                    // them so we can query the profiler to know if the object
                    // is alive or not at any given time 
            _watch_x1 = _profiler.addWatch(x1);
            _watch_x2 = _profiler.addWatch(x2);

        }

    }
}

import flash.display.Sprite;

class Foo {

    public var someProp:Sprite;
}

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

0 голосов
/ 03 октября 2010

После долгих размышлений на выходных, я понял, в чем проблема.

По сути, у нас есть такой сценарий:

.--------------.
| APP-DOMAIN 1 |
| [FooFactory] |
'--------------'
       | 
       | < [object Foo]
       |
.--------------.
| APP-DOMAIN 2 |
| [MyModule]   |
'--------------'

APP-DOMAIN 1 всегда остается в памяти, поскольку он загружен в максимально возможной области приложения: исходный скомпилированный код SWF. APP-DOMAIN 2 загружается в память и из нее динамически и должен иметь возможность полностью отделить себя от APP-DOMAIN 1. Согласно гениальному ответу Хуана Пабло Калифано, приведенному выше, APP-DOMAIN 2 со ссылкой на [object Foo] не обязательно связывает APP-DOMAIN 2 с памятью, хотя оно может стать связанным с памятью путем добавления [MyModule] слушатель события [object Foo], верно?

Ладно, с учетом этого решение с избыточным количеством ресурсов должно было бы вернуть реализацию-1018 * для слабых ссылок из метода getFoo, поскольку в этом случае вещи должны "прерваться" в случае " чрезвычайная ситуация." (С этой точки зрения вещи должны быть слабыми, чтобы APP-DOMAIN 1 мог быть полностью собран мусором при выгрузке.) Опять же, это излишний ответ.

Однако мне не нужно сохранять слабый реф до Foo в FooFactory, поскольку FooFactory нуждается в , чтобы иметь надежный способ получить удержание каждого созданного Foo объекта. Короче говоря, теория Хуана Пабло Калифано абсолютно верна, ее просто нужно проверить в реальном мире, чтобы все окончательно доказать:)


Помимо всего этого, я полагаю, что я раскрыл реальную проблему за кулисами, которая привела к тому, что подобная библиотека, которую я написал в прошлом, никогда не собиралась GC. Проблема была не в той библиотеке, которую я написал, но, похоже, она была в библиотеке отражений, которую я использовал. Библиотека отражений будет «кэшировать» каждый объект Class, который я бросил, поскольку мой оригинальный метод FooFactory.getFoo принимал параметр Class, а не String. Поскольку библиотека, казалось, жестко ссылалась на каждый Class объект, передаваемый в память, я почти уверен, что это была утечка памяти.


В заключение, как последний вопрос, верно ли для меня сказать, что для подсчета ссылок не существует решения AS3? Я создаю полный пакет модульного тестирования для этой библиотеки, и если бы я мог сделать что-то вроде Assert.assertEquals(0, getReferenceCount(foo)), это было бы рад.

0 голосов
/ 30 сентября 2010

Поскольку вам, по сути, нужны слабые ссылки, возможно, лучшим решением будет использование одной из слабых ссылок, доступных в AS3.

Например, ваш метод должен хранить Словари , а не фактические объекты. Как то так:

private var allFoos:Dictionary;

public function getFoo(key:String):Foo {
    var f:Foo = _getFoo(key);

    if (f == null) {
        f = _createFoo(key);
    }

    return f;
}

private function _createFoo(key:String):Foo {
    var f:Foo = new Foo();
    var d:Dictionary = new Dictionary(/* use weak keys */ true);
    d[f] = key;

    allFoos[key] = d;
}
...