Я не думаю, что шаблон, который вы представили, должен вызывать проблемы 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 и последующей проверке того, сколько объектов все еще живы. Проверьте этот ответ , чтобы увидеть, как это можно реализовать. Основное отличие состоит в том, что это дает вам меньше контроля, но, возможно, этого достаточно для ваших целей. Как бы то ни было, мне хотелось попробовать другую идею, поэтому я написал этот наблюдатель объекта и вроде как идея.