2 Stimmen

Flash Player: Referenzanzahl für Variable abfragen

Ich bin auf der Suche nach einer Bibliothek zu bauen, die sehr vorsichtig über die Speicherverwaltung sein muss. Im Grunde, I haben eine statische Fabrik zu erstellen, um Instanzen meines Werkzeugs an anfordernde Objekte zu "verteilen". (Ich habe keine Wahl in dieser Angelegenheit, ich muss wirklich ein Singleton verwenden) Wir nennen diese Klasse FooFactory . FooFactory definiert eine einzige Methode, getFoo(key:String):Foo .

getFoo sieht in einem privaten statischen flash.utils.Dictionary Objekt für das entsprechende Foo Instanz und gibt sie entweder durch Lazy-Instantiierung oder einfach zurück. In jedem Fall, FooFactory MUSS einen Verweis auf jede Foo Instanz erstellt, so dass alle Foo Instanzen können aktualisiert werden durch FooFactory mit einer Methode namens updateFoos():void .

Hier ist ein Pseudocode, der zeigt, wovon ich spreche:

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"));
            }
        }
    }
}

Die eigentliche Funktion und Identität der Foo ist nicht allzu wichtig.

Was IS Wichtig ist in dieser Situation die Garbage Collection. Ich habe in der Vergangenheit etwas Ähnliches wie das obige Beispiel erstellt und hatte unglaubliche Probleme mit der Garbage Collection. (I hat ein Array statt eines Wörterbuchs verwenden, was ein Teil des Problems sein könnte). In meiner Flex-Anwendung würden die Module nie entladen, da die Instanzen einen Verweis auf ein Foo Instanz, auf die die FooFactory , etwa so: (wieder Pseudocode)

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

Was ich wissen möchte, sind die beiden folgenden Dinge:

  1. Ist der obige Pseudocode "garbage-collector-sicher"? IE: Werden meine Module ordnungsgemäß entladen und werden die Instanzen des Group Unterklasse oben wird Müll gesammelt?
  2. Gibt es eine Möglichkeit, in Flash Player (auch in der Debug-Player, wenn nötig), die mich beim Zählen von Verweisen unterstützen kann, so dass ich testen kann, ob die Dinge immer Garbage Collect oder nicht?

Ich bin mir bewusst, dass die flash.sampler API, aber ich bin mir nicht sicher, wie ich sie zum Zählen von Verweisen verwenden kann.

1voto

Juan Pablo Califano Punkte 12074

Ich glaube nicht, dass das von Ihnen vorgestellte Muster Ihnen GC-technisch Probleme bereiten sollte.

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

Hier hat Ihr Modul einen Verweis auf eine Foo-Instanz. Das bedeutet, dass diese Foo-Instanz nicht sammelbar ist, solange dein Modul nicht sammelbar ist. Das Modul hat einen Verweis auf foo also hier foo erreichbar ist (wenn das Modul erreichbar ist). Umgekehrt ist das nicht der Fall. Selbst wenn foo ewig lebt, hat es keinen Verweis auf das Modul, so dass es es nicht einspeichern wird.

Natürlich könnte es andere Gründe geben, die verhindern, dass Ihr Modul gesammelt werden kann, aber foo ist hier nicht der Übeltäter, es sei denn, foo erhält irgendwie einen Verweis auf das Modul. Zum Beispiel fügt das Modul einen Listener zu foo was in diesem Fall dasselbe ist wie Schreiben:

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

Die Tatsache, dass Sie die Instanz als const sollte die Dinge auch nicht per se ändern. Es bedeutet nur, dass die gespeicherte Referenz später nicht mehr geändert werden kann. Wenn Sie jedoch die Referenz löschen wollen foo zu einem späteren Zeitpunkt, können Sie das nicht, weil das eine Neuzuweisung des Verweises wäre; und Sie können keine Neuzuweisung eines const Referenz (Sie sollten einen Compilerfehler erhalten). Nun, dies bindet foo zum Modul. Solange Ihr Modul aktiv ist, hat es einen Verweis auf foo Así que foo wird nicht sammelbar sein.

Bezüglich dieser Zeile:

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

Es sieht so aus, als ob Sie versuchen, eine Art Cache zu bauen. Ich bin mir nicht sicher, ob Sie hier schwache Referenzen verwenden wollen. (Ich könnte hier falsch sein, weil ich eine Annahme mache, und man sagt, Annahme ist die Mutter aller... Fehler, aber ich schweife ab)

In jedem Fall hat dies zur Folge, dass, wenn ein Modul eine Foo und zu einem bestimmten Zeitpunkt das Modul erfolgreich entladen (d.h. aus dem Speicher entfernt) wird, wird diese Instanz von foo gesammelt werden, vorausgesetzt, dass niemand sonst einen Verweis darauf hat (d.h. der einzige Weg, ihn zu erreichen, ist über den Wörterbuchschlüssel, aber da die Schlüssel schwach referenziert sind, zählt dieser Verweis nicht für die Zwecke der GC).

In Bezug auf Ihre zweite Frage würde ich den FlexBuilder/FlashBuilder-Profiler empfehlen, wenn FB für Sie verfügbar ist. Zugegeben, es ist nicht das intuitivste Tool, aber mit etwas Übung kann es sehr nützlich sein, um Speicherprobleme zu erkennen. Grundsätzlich erfahren Sie damit, wie viele Instanzen einer bestimmten Klasse erstellt wurden, wie viele davon noch am Leben sind, welche Objekte auf diese Instanzen verweisen und wo all diese Objekte alloziert wurden (eine Option, die standardmäßig nicht aktiviert ist, wenn Sie den Profiler starten, aber sehr nützlich ist, um ein Leck aufzuspüren).

PS

Bezüglich Ihres Kommentars:

Vielleicht ist die Realität const-Referenz, die durch die Group Instanz gebunden ist? Wenn das ein Problem ist, könnte ich Foo einfach zu einer Schnittstelle abstrahieren, und dann etwas namens FooWeakReference erstellen, das ein schwaches Wörterbuch zum Verweis auf das Foo-Objekt verweist. Was denken Sie?

Das Hinzufügen dieser zusätzlichen indirekten Ebene verkompliziert die Dinge nur und macht Ihren Code unübersichtlicher, ohne dass Sie dadurch etwas gewinnen, denke ich. Es ist einfacher, den Lebenszyklus Ihres Moduls zu berücksichtigen und klare Punkte der Initialisierung und Finalisierung zu definieren. Wenn es abgeschlossen ist, stellen Sie sicher, dass Sie jeden Verweis auf das Modul entfernen, das der foo Instanz (d.h. wenn Sie Hörer auf foo entfernen, usw.), so dass Ihr Modul unabhängig vom Lebenszyklus von foo .

Generell gilt: Wann immer ein schwacher Verweis einen Fehler in Ihrer Anwendung zu lösen scheint, verdeckt er einen anderen oder vertuscht ein schlechtes Design; es gibt Ausnahmen (und Kompromisse, die manchmal gemacht werden müssen), aber schwache Verweise werden grundlos missbraucht, wenn Sie mich fragen; ich weiß, dass nicht jeder zustimmen wird.

Außerdem eröffnen weak-refs eine ganz neue Art von Fehlern: Was passiert, wenn die Instanz, die Sie erstellt haben, faul verschwindet, bevor Sie sie benutzen können oder schlimmer noch, während Sie sie benutzen? Ereignis-Listener, die unter nicht deterministisch reproduzierbaren Umständen nicht mehr funktionieren (z.B. wenn Sie einen Listener zu einem Objekt hinzugefügt haben, das nicht mehr existiert), mögliche Null-Referenzen (z.B. wenn Sie versuchen, einen Listener zu einem Objekt hinzuzufügen, das nicht mehr existiert), etc, etc. Trinken Sie nicht das schwache Verweismedium ;).

Addedum

Zusammenfassend kann man sagen, dass Ist es richtig, wenn ich sage, dass keine AS3 Lösung für das Zählen von Referenzen gibt? Ich baue gerade eine komplette Unit-Testing-Suite für diese Bibliothek Ich baue, und wenn ich tun könnte etwas wie Assert.assertEquals(0, getReferenceCount(foo)) machen könnte, wäre das toll.

Nun, ja. Sie können die Anzahl der Verweise auf ein bestimmtes Objekt nicht von Actionscript abrufen. Selbst wenn es möglich wäre, bin ich mir nicht sicher, ob das helfen würde, denn die Referenzzählung ist nur ein Teil der Funktionsweise von GC. Der andere Teil ist ein Markierungs- und Sweep-Algorithmus. Wenn also ein Objekt eine Referenzanzahl von Null hat, ist es sammelbar, aber es könnte, sagen wir, 3 Referenzen haben und immer noch sammelbar sein. Um wirklich festzustellen, ob ein Objekt sammelbar ist oder nicht, sollte man sich in die GC-Routine einklinken können, und das ist von AS aus nicht möglich.

Außerdem wird dieser Code niemals funktionieren.

Assert.assertEquals(0, getReferenceCount(foo)

Warum? Sie versuchen hier, eine API abzufragen, um zu erfahren, ob ein Objekt ist sammelbar oder nicht. Da Sie das nicht wissen können, nehmen wir an, dass dies Ihnen sagt, ob ein Objekt wurde gesammelt oder nicht. Das Problem ist, foo zu diesem Zeitpunkt entweder null oder nicht null ist. Wenn es null ist es keine gültige Referenz, so dass Sie aus offensichtlichen Gründen keine nützlichen Informationen daraus ziehen können. Wenn es nicht null ist, ist es ein gültiger Verweis auf ein Objekt, dann können Sie darauf zugreifen und es ist lebendig; Sie kennen also bereits die Antwort auf die Frage, die Sie stellen.

Ich glaube, ich verstehe jetzt Ihr Ziel. Sie möchten in der Lage sein, programmgesteuert festzustellen, ob bestimmte Objekte durchgesickert sind. Bis zu einem gewissen Grad ist das möglich. Dazu muss die flash.sampler-API verwendet werden, wie Sie in Ihrer ursprünglichen Frage erwähnten.

Ich schlage vor, Sie sehen sich die Flash Preload Profiler von jpauclair:

Ich habe es nicht benutzt, aber es sieht so aus, als ob es genauso gut wie der FB-Profiler für die Speicherüberwachung sein könnte.

Da es sich um Actionscript-Code handelt (und da er Open Source ist), können Sie ihn für Ihre Zwecke verwenden. Ich habe den Code nur überflogen, aber ich konnte ein sehr einfaches Proof of Concept erstellen, indem ich monkey-Parcheando die SampleAnalyzer Klasse:

Es gibt noch viele andere Dinge, die in diesem Tool passieren, aber ich habe gerade den Memory Analizer so verändert, dass er eine Liste der lebenden Objekte zurückgeben kann.

Ich habe also eine einfache Klasse geschrieben, die diesen Profiler ausführt. Die Idee ist, dass man beim Erstellen eines Objekts diese Klasse bitten kann, es zu beobachten. Die Zuordnungs-ID dieses Objekts wird in der Tabelle der zugewiesenen Objekte, die vom Speicher-Profiler verwaltet wird, nachgeschlagen und ein Handle darauf wird lokal gespeichert (nur die ID) . Diese id Der Einfachheit halber wird auch der Griff zurückgegeben. Sie können also speichern diese id Handle und verwenden Sie es zu einem späteren Zeitpunkt, um zu prüfen, ob das Objekt abgeholt wurde oder nicht. Außerdem gibt es eine Methode, die eine Liste aller Handles zurückgibt, die Sie hinzugefügt haben, und eine weitere, die eine Liste der hinzugefügten Handles zurückgibt, die auf lebende Objekte zeigen. Ein Handle ermöglicht Ihnen den Zugriff auf das ursprüngliche Objekt (wenn es noch nicht abgeholt wurde), seine Klasse und auch den Allokations-Stack-Trace. (Ich speichere weder das Objekt selbst noch das NewObjectSample-Objekt, um zu vermeiden, dass es versehentlich festgehalten wird)

Das ist wichtig: Diese Abfrage gilt für lebende Objekte . Die Tatsache, dass ein Gegenstand lebendig ist, bedeutet nicht, dass er nicht sammelbar ist. Das allein bedeutet also nicht, dass es ein Leck gibt. Es könnte zu diesem Zeitpunkt lebendig sein, aber das bedeutet immer noch nicht, dass es ein Leck gibt. Sie sollten dies also mit dem Erzwingen von GC kombinieren, um relevantere Ergebnisse zu erhalten. Außerdem könnte dies von Nutzen sein, wenn Sie Objekte beobachten, die Ihnen gehören und nicht mit anderem Code (oder anderen Modulen) geteilt werden.

Hier ist also der Code für den ProfileRunner, mit einigen Kommentaren.

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 + "]";
    }
}

Und hier ist eine einfache Demonstration, wie Sie es verwenden können.

Er initialisiert den Läufer, weist 2 Foo-Objekte zu und beendet sich dann nach 2 Sekunden selbst. Beachten Sie, dass ich im Finalizer eines der Foo-Objekte lösche und den Profiler finalisiere. Dort versuche ich, GC zu erzwingen, warte einige Zeit (GC ist nicht synchron) und prüfe dann, ob diese Objekte noch am Leben sind. Das erste Objekt sollte false zurückgeben, das zweite true. Dies ist also der Ort, an dem Sie Ihre Assert einfügen sollten. Beachten Sie, dass all dies nur in einem Debug-Player funktioniert.

Hier ist also ohne weiteres der Beispielcode:

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;
}

Alternativ ist ein leichterer Ansatz für die Verfolgung lebendiger Objekte, sie in einem schwach referenzierten Wörterbuch zu speichern, GC zu erzwingen und dann zu überprüfen, wie viele Objekte noch lebendig sind. Ausprobieren diese Antwort um zu sehen, wie dies umgesetzt werden könnte. Der Hauptunterschied besteht darin, dass Sie dadurch weniger Kontrolle haben, aber vielleicht reicht das für Ihre Zwecke aus. Wie auch immer, ich fühlte mich wie die andere Idee einen Versuch zu geben, so schrieb ich dieses Objekt Watcher und Art von wie die Idee.

0voto

Michael Brewer-Davis Punkte 13669

Da Sie im Wesentlichen schwache Referenzen wünschen, wäre die beste Lösung vielleicht eine der in AS3 verfügbaren schwachen Referenzen.

Lassen Sie Ihre Methode zum Beispiel Folgendes speichern Wörterbücher und nicht die eigentlichen Objekte. Etwa so:

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;
}

0voto

Naftuli Kay Punkte 82152

Ich habe am Wochenende intensiv nachgedacht und glaube, dass ich das Problem gefunden habe.

Im Wesentlichen haben wir folgendes Szenario:

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

APP-DOMAIN 1 bleibt immer im Speicher, da es in der höchstmöglichen Anwendungsdomäne geladen wird: dem ursprünglichen kompilierten Code einer SWF. APP-DOMAIN 2 wird dynamisch in den und aus dem Speicher geladen und muss in der Lage sein, sich vollständig von APP-DOMAIN 1 . Laut der obigen genialen Antwort von Juan Pablo Califano, APP-DOMAIN 2 mit einem Verweis auf [object Foo] nicht unbedingt Krawatte APP-DOMAIN 2 in den Speicher zu übertragen, obwohl sie durch [MyModule] Hinzufügen eines Ereignis-Listeners zu [object Foo] richtig?

Okay, also, mit diesem Gedanken im Hinterkopf, wäre eine Overkill-Lösung, eine weak-reference-Implementierung von Foo von der getFoo Methode, da die Dinge im "Notfall" an dieser Stelle "abbrechen" müssen. (Die Dinge müssen aus dieser Perspektive schwach sein, damit APP-DOMAIN 1 kann beim Entladen vollständig entsorgt werden). Auch dies ist eine überflüssige Antwort.

Aber ich nicht müssen einen weak-ref auf Foo en FooFactory da FooFactory braucht einen sicheren Weg zu finden, um an jedes erstellte Dokument heranzukommen Foo Gegenstand. Kurz gesagt, Juan Pablo Califano hat mit seiner Theorie völlig recht, sie muss nur noch in der realen Welt getestet werden, um alles endgültig zu beweisen :)


Abgesehen davon glaube ich, dass ich das eigentliche Problem hinter den Kulissen aufgedeckt habe, das dazu geführt hat, dass eine ähnliche Bibliothek, die ich in der Vergangenheit geschrieben habe, nicht auf GC läuft. Das Problem lag nicht in der eigentlichen Bibliothek, die ich geschrieben habe, sondern es scheint, dass es in einer Reflection-Bibliothek lag, die ich verwendet habe. Die Reflection-Bibliothek "zwischenspeichert" jede Class Objekt, das ich darauf geworfen habe, da mein ursprüngliches FooFactory.getFoo Methode hat eine Class Parameter, anstatt eines String . Da die Bibliothek scheinbar jeden Artikel hart referenziert Class Objekt in den Speicher übergeben wurde, bin ich mir ziemlich sicher, dass dies das Speicherleck war.


Abschließend noch eine letzte Frage: Stimmt es, dass es keine AS3-Lösung für das Zählen von Referenzen gibt? Ich baue eine komplette Unit-Testing-Suite für diese Bibliothek, die ich baue, und wenn ich etwas tun könnte wie Assert.assertEquals(0, getReferenceCount(foo)) Das wäre toll.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X