Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox
EN CHANTIER
Cette page n'est pas terminée et est en cours d'écriture.

Optimiser la mémoire

Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Compatible Flex 3. Cliquer pour en savoir plus sur les compatibilités.Compatible Flex 4. Cliquer pour en savoir plus sur les compatibilités.Par deuxsucres, le 05 novembre 2011

Optimiser la mémoire utilisée par un programme consiste en deux principes : libérer la mémoire et éviter les déplacements mémoire.

Pour contrôler ce qui se passe en mémoire et vérifier que les deux principes sont appliqués lors de l'exécution du programme, nous avons besoin d'un outil spécifique : le Profiler.

Pré-requis généraux
  • bonnes connaissances en Programmation Orientée Objet.
  • bonnes connaissances des packages, classes, instances…
et pour suivre les démonstrations
  • maitrise de l'environnement de développement Flash Builder.

Pour illustrer le tutoriel, nous proposons des exemples de codes ActionScript 3 et des démonstrations. Les exemples de codes sont donnés purement à titre d’illustration, ils ne sont pas forcement fonctionnels.

Pour utiliser les démonstrations, vous aurez besoins de Flash Builder 4.5 Premium (version qui intègre le Profiler) et du SDK 4.5.1. Lorsque nous parlerons de lancer une application de démonstration, nous faisons référence aux applications livrées dans le projet Flash Builder que vous pouvez télécharger ci-dessous. Si vous souhaitez déboguer dans un navigateur, il est conseillé d’utiliser Internet Explorer 9 sous Windows qui pose moins de problème que Firefox lors de l’utilisation du Profiler.

Le Profiler

Outil indispensable à l’optimisation de la mémoire, le Profiler se connecte à la version debugger du Flash Player et affiche les informations d’utilisation de la mémoire.

Lancer et paramétrer le Profiler

Pour déboguer une application en mode Profiler, utilisez la commande Profil du menu Exécuter de Flash Builder.

Une fois la connexion du Profiler au debugger Flash Player établie, une fenêtre d’options s’ouvre :

Les options qui nous intéressent sont : Activer le profilage de la mémoire et Surveiller les données de mémoire active. Veillez à ce que ces deux options soient cochées puis cliquez sur reprendre.

Votre application doit maintenant s’exécuter dans le navigateur et Flash Builder doit vous ouvrir la perspective Profil Flash :

Tout à droite de l’onglet Objets actifs, cliquez sur le bouton Filtres (avant dernier bouton). Cette fenêtre d’options doit s’ouvrir :

Vérifiez que vous avez la même configuration que sur l’image ci-dessus. Les options ici sélectionnées permettent de filtrer l’affichage des objets actifs et évite que les objets des packages flash, mx et spark soient affichés dans la liste. Seuls nos objets personnalisés apparaîtront, facilitant ainsi l’identification des informations mémoire des objets qui nous intéressent.

Description du Profiler

Nous parlons de sessions de profilage et vous remarquerez que Flash Builder garde l’historique des précédentes sessions de profilage. Nous ne décrirons ici que les éléments intéressants pour les démonstrations du tutoriel.

Onglet Profiler

Cet affichage présente toutes les sessions de profilage précédentes et toutes celles actives. Si vous sélectionnez une session, vous aurez accès aux boutons suivants, qui en fonction de l’état de la session de profilage, seront actifs ou inactifs :

Les boutons qui nous intéressent sont les 3 premiers et le dernier qui permettent respectivement de :

  • Reprendre l’exécution de l’application après l’avoir mise en pause
  • Mettre en pause l’application
  • Arrêter la session de profilage
  • Supprimer la session de l’historique. Utile lorsqu’au bout d’un moment, de nombreuses sessions sont affichées. Ce bouton n’est disponible que lorsque la session concernée est arrêtée.

Onglet Objets actifs

Cet onglet affiche les objets en mémoire de la session de profilage sélectionnée. Comme nous avons précédemment défini des filtres, seuls les objets non issus des packages flash, mx et spark sont affichés.

Description des colonnes :

  • Classe : nom de la classe
  • Package : nom du package
  • Occurrences cumulées : nombre de fois que la classe a été instanciée depuis le lancement de l’application
  • Occurrences : nombre d’occurrences (d’instances) de la classe actuellement en mémoire
  • Mémoire cumulée : total de la mémoire utilisée par cette classe depuis le lancement de l’application
  • Mémoire : mémoire actuellement utilisé par les occurrences de la classe.

Un bouton va nous être utile parmi ceux tout à droite de l’onglet Objets actifs. Il s’agit du bouton Lancer la récupération de place, le premier de la liste. Il sert à forcer le passage du ramasse-miettes, nous aurons à l’utiliser au cours de quelques démonstrations.

Utilisation des démonstrations

Comme vous le verrez un peu plus tard, la mise à jour de l’affichage dans le Profiler n’intervient pas à chaque manipulation de l’application. Il y aura toujours un décalage entre le moment où vous effectuerez une opération dans l’application et le moment où l’affichage sera mis à jour.

Libérer la mémoire

Dans les environnements d’exécution Flash (Flash Player et Air), la gestion de la mémoire est déléguée au ramasse-miettes (garbage collector). Ce ramasse-miettes est un processus géré en tache de fond par le moteur d’exécution qui a la responsabilité de supprimer de la mémoire tout objet inutilisé.

Dans un tel environnement, le développeur ne peut pas directement supprimer un objet de la mémoire, il doit s’assurer que l’objet n’est plus utilisé pour que le ramasse-miettes puisse le supprimer. Une bonne compréhension des valeurs primitives/complexes et du fonctionnement du ramasse-miettes est indispensable pour libérer convenablement la mémoire.

Rappel sur les valeurs primitives et valeurs complexes

Une valeur primitive est une valeur de type Boolean, String, Number, uint ou int. Tous les autres types de données sont des valeurs complexes (Array, Date, XML…) Les valeurs primitives sont passées par valeur alors que les valeurs complexes sont passées par référence.

Exemple de passage par valeur
var a:String = "chaine de caractères" ;
var b:String = a ;
var c:String = a ;
trace(b) ; // chaine de caractères
trace(c) ; // chaine de caractères
b += "b" ;
c += "c" ;
trace(a) ; // chaine de caractères
trace(b) ; // chaine de caractères b
trace(c) ; // chaine de caractères c

Nous utilisons dans cet exemple le type de valeur primitive String. La valeur chaine de caractères de la variable a est donc passé par valeur aux variables b et c, elle est copiée dans b et dans c. Une modification d’une des variables a, b ou c n’entraine donc pas de modification des valeurs des autres variables.

Exemple de passage par référence
var a:Array = new Array() ;
var b:Array = a ;
var c:Array = a ;
trace(a.lenght) ; //0
trace(b.lenght) ; //0
trace(c.lenght) ; //0
b.push("test b") ;
c.push("test c") ;
trace(a.lenght) ; //2
trace(b.lenght) ; //2
trace(c.lenght) ; //2

Nous utilisons dans cet exemple le type de valeur complexe Array. La valeur de type Array de la variable a est donc passé par référence aux variables b et c, les trois variables a, b et c font donc référence au même objet en mémoire. Une modification d’une des variables a, b ou c entraine donc une modification de l’objet Array en mémoire référencé par les variables a, b et c.

Pour en savoir plus, rendez-vous dans la documentation d’ActionScript : Formation à ActionScript 3.0 / Syntaxe et langage ActionScript / Types de données.

Le ramasse-miettes

Nombre de références

Le ramasse-miettes compte le nombre de fois qu’un objet est référencé. Seules les valeurs complexes sont donc concernées. Si un objet n’est plus référencé, le ramasse-miettes le supprime de la mémoire.

Exemple
var a:Object = new Object() ;
var b:Object = a ;
//La variable a fait référence à un objet de type Object.
//La variable b fait également référence à cet objet.
//L’objet est donc référencé deux fois, le ramasse-miettes ne le supprimera pas de la mémoire.
a = null ;
//La variable a ne fait plus référence à l’objet.
//L’objet n’est donc référencé plus qu’une fois, le ramasse-miettes ne le supprimera pas de la mémoire.
b = null ;
//La variable b ne fait plus référence à l’objet.
//L’objet n’est donc plus référencé, le ramasse-miettes le supprimera de la mémoire.
Démonstration

Dans cette démonstration qui met en pratique l'exemple ci-dessus, nous utiliserons la classe personnalisée garbagecollector.Referencer.

Lancez en mode Profiler l’application Reference.

Vous constaterez dans la liste des objets actifs qu’une occurrence de Referencer est actuellement en mémoire. Cette occurrence a deux références : A et B.

  • Cliquez sur le bouton Supprimer la référence A de l’application. La référence A a été supprimée, mais l’occurrence de Referencer est toujours en mémoire.
  • Cliquez sur le bouton Supprimer la référence B de l’application. La référence B a été supprimée et plus aucune occurrence de Refencer n’est encore en mémoire.

Références en chaine

Il y a référence en chaine lorsque un objet fait référence à un autre objet qui fait référence à un autre objet qui fait référence à un autre objet…

Exemple de références en chaine
var a:Object = new Object () ; //instance Object A
var b:Object = new Object () ; // instance Object B
var c:Object = new Object () ; // instance Object C
a.ref = b ;
b.ref = c ;
//La variable a fait référence à l’objet A dont la propriété ref fait référence à l’objet B dont la propriété ref fait référence à l’objet C.
//Autrement dit, a référence b qui référence c .
b = null ;
c = null ;
//Les 3 objets ne seront pas supprimés de la mémoire puisque chacun d'eux compte toujours au moins une référence :
//la variable a fait toujours référence à l’objet A qui fait référence à l’objet B qui fait référence à l’objet C. 
a = null ;
//Ce coup-ci, la référence à l’objet A est supprimée, le ramasse-miettes supprimera donc l’objet A de la mémoire,
//supprimant de fait la référence à l’objet B qui pourra également être supprimé de la mémoire,
//entrainant la suppression de la référence à l’objet C qui sera à son tour supprimé de la mémoire.
Démonstration

Dans cette démonstration, nous utiliserons les classes personnalisées garbagecollector.Referencer et garbagecollector.Target. Les objets A et B de l’exemple ci-dessus seront des instances de Referencer et l’objet C sera une instance de Target.

Lancez en mode Profiler l’application ReferenceEnChaine.

Vous constaterez dans la liste des objets actifs que 2 occurrences de Referencer et une occurrence de Target sont actuellement en mémoire.

  • Cliquez sur le bouton Supprimer la référence B de l’application. La référence B a été supprimée, mais l’occurrence de Referencer correspondante est toujours en mémoire. Il y toujours 2 occurrences de Referencer et une occurrence de Target en mémoire.
  • Cliquez sur le bouton Supprimer la référence C de l’application. La référence C a été , mais l’occurrence de Target correspondante est toujours en mémoire. Il y toujours 2 occurrences de Referencer et une occurrence de Target en mémoire.
  • Maintenant, cliquez sur le bouton Supprimer la référence A de l’application. La référence A a été supprimée et les 2 occurrences de Referencer et l’occurrence de Target ont été supprimés de la mémoire.

Références circulaires

Il y a référence circulaire lorsqu’au moins deux objets se font référence l’un à l’autre. Reprenons l’exemple précédent et créons une référence circulaire.

Exemple de référence circulaire
var a:Object = new Object() ; //instance Object A
var b:Object = new Object() ; // instance Object B
var c:Object = new Object() ; // instance Object C
a.ref = b ;
b.ref = c ;
c.ref = a ;
//L’objet A référence l’objet B qui référence l’objet C qui référence l’objet A.
//Chaque objet est donc référencé par une des variables a, b et c et par un des autres objets.
a = null ;
b = null ;
c = null ;
//Les trois objets A, B et C ne sont plus référencés par les variables,
//mais ils sont toujours référencés entre eux via les propriétés ref de chaque Objet.
//Ils comptent donc toujours au moins une référence.

Dans ce cas, il y a référence circulaire entre les trois objets et depuis la version 8 du Player Flash, le ramasse-miettes est capable de détecter ces références circulaires et de supprimer de la mémoire les objets concernés. Mais cette détection peut-être longue, très longue, ce qui peut provoquer quelques problèmes.

Démonstration

Lancez en mode Profiler l’application ReferenceCirculaire.

Vous constaterez dans la liste des objets actifs qu'il y a 3 occurrences de Referencer.

  • Cliquez sur le bouton Supprimer la référence A de l’application. La référence A a été supprimée, mais l’occurrence de Referencer correspondante est toujours en mémoire. Il y toujours 3 occurrences de Referencer.
  • Cliquez sur le bouton Supprimer la référence B de l’application. La référence B a été supprimée, mais l’occurrence de Referencer correspondante est toujours en mémoire. Il y toujours 3 occurrences de Referencer.
  • Cliquez sur le bouton Supprimer la référence C de l’application. La référence C a été supprimée. A cette instant il y toujours 3 occurrences de Referencer car A fait référence à B qui fait référence à C qui fait référence à A.

Le ramasse-miettes est capable de détecter cette référence circulaire, mais cette détection peut prendre du temps. Si dans le Profiler vous avez toujours 3 occurrences de Referencer après plusieurs secondes, cliquez sur le bouton Lancer la récupération de place pour forcer le ramasse-miettes à recalculer les références et supprimer les objets en mémoire. Les occurrences de Referencer devraient être supprimées de la mémoire.

Exécution du ramasse-miettes

Le ramasse-miettes est exécuté régulièrement par le moteur d’exécution en fonction de certains paramètres dont le taux d’utilisation de la mémoire. Il n’est pas possible de déterminer quand il sera activé ni de forcer son exécution (Nous avons vu que les outils de débogage permettent de forcer le passage du ramasse-miettes, mais en production il est impossible de le forcer).

Tant que le ramasse-miettes n’est pas exécuté, tous les objets en mémoire continuent à s’exécuter. Comme nous l'avons vu avec les références circulaires, le ramasse-miettes peut-être long à supprimer de la mémoire certains objets. Cette latence peut poser certains problème.

Démonstration de la latence

Lancez en mode Débogage ou Profiler l’application ReferenceCirculaire2.

Basés sur le même code que la démonstration précédente, nous avons ajouté une barre de progression qui indique l'activité de l'objet C. Tant que l'objet C est en mémoire, la barre de progression continuera d'avancer.

  • Cliquez sur le bouton Supprimer les références A, B et C. Les références aux objets A, B et C ont été supprimées, mais la référence circulaire entre ces trois objets persiste. Les objets restent en mémoire et la barre de progression avance…
  • Cliquez sur le bouton Provoquer le passage du ramasse-miettes. Le ramasse-miettes recalcule les références, détecte la référence circulaire et supprime les 3 objets de la mémoire. La barre de progression est stoppée.

Attention donc à la latence entre le moment où vous supprimer la dernière référence à un objet et le moment où cet objet et effectivement supprimé de la mémoire. Si vous jouer un son par exemple et que vous supprimé la dernière référence à l'objet qui lit le son, vous entendrez le son tant que le ramasse-miettes n'aura pas supprimer l'objet de la mémoire.

Provoquer les suppressions de références

Dans certains cas, notamment lors de références circulaires, il peut-être nécessaire de prévoir dans les objets une méthode provoquant la suppression des références. Reprenons l'exemple des références circulaires et ajoutons une méthode release de suppression de référence.

Exemple
var a:Object = new Object() ; //instance Object A
a.release = function():void {this.ref = null} ;
var b:Object = new Object() ; // instance Object B
b.release = function():void {this.ref = null} ;
var c:Object = new Object() ; // instance Object C
c.release = function():void {this.ref = null} ;
a.ref = b ;
b.ref = c ;
c.ref = a ;
//Maintenant, avant de supprimer les références, appelons la méthode release() des objets
//avant de supprimer leur référence.
a.release() ;
a = null ;
b.release() ;
b = null ;
c.release() ;
c = null ;
//Les trois objets ont nettoyé leurs références et les A, B et C ne sont plus référencés par les variables.
//Les trois objets n'ont plus de références, ils seront supprimés de la mémoire.
Démonstration

Lancez en mode Débogage ou Profiler l’application Release.

Basés sur le même code que la démonstration ReferenceCirculaire2, nous avons ajouté une méthode release() aux objets et nous appelons cette méthode avant de supprimer une référence.

  • Cliquez sur le bouton Supprimer les références A, B et C. La méthode release() est appelée sur les objets A, B et C et les références à ces objets sont supprimées. Il n'y a plus de référence circulaire ni de référence, les objets sont supprimés de la mémoire et la barre de progression s'arrête quasi-instantanément.

Les fonctions

En ActionScript 3, tout est objet. Même les fonctions sont des objets ! Les fonctions peuvent donc être référencées.

Les références de fonctions sont principalement utilisées pour des méthodes de rappel.

Exemple
//Instance de l'objet écouteur
var handler:Object = new Object() ;
handler.callBackFunction = function():void { } ;
 
//Instance de l'objet appelant et définition de la méthode de rappel
var caller:Object = new Object() ;
caller.callBack = handler.callBackFunction ;
 
//Suppression de la référence à l'objet écouteur
handler = null ;

L'objet appelant référence la méthode callBackFunction de l'objet écouteur, l'objet appelant fait donc référence à l'objet écouteur via la méthode callBackFunction. L'objet écouteur ne sera supprimé de la mémoire que lorsque la référence à sa méthode callBackFunction sera supprimé.

Démonstration

Dans cette démonstration qui met en pratique l'exemple ci-dessus, nous utiliserons les classes personnalisées functionreference.Caller et functionreference.Handler.

Lancez en mode Profiler l’application ReferenceFunction.

Vous pouvez constater qu'il y a en mémoire une référence de l'objet Caller et une référence de l'objet Handler.

  • Cliquez sur le bouton Libérer la méthode référencée. La méthode release() est appelée sur l'objet Caller qui libère alors sa référence à la fonction de rappel. L'objet Handler en mémoire est alors supprimé.
Fonctions anonymes

Attention également aux fonctions anonymes ! Pensez à supprimer leurs références pour qu'elles soient supprimées de la mémoire.

Les fonctions anonymes sont difficile à maitriser et peuvent donc poser problème en mémoire. Elles sont à éviter.

Conclusion

Pour garantir une utilisation minimale de la mémoire, il faut veillez à supprimer toutes les références à un objet que l’on souhaite voir supprimé de la mémoire. Attention à certains objets que l'on ne soupçonne pas, tels que les fonctions.

Nous avons vu que les références en chaine ne posent pas de problème mais qu'il faut se méfier des références circulaires.

Pour éviter les mauvaises surprises, il faut bien faire attention à ce qu’un objet peut exécuter comme code entre le moment où sa dernière référence est supprimée et le moment où le ramasse-miettes le supprimera de la mémoire. Pour cela, une méthode qui provoque les suppressions de références peut faciliter le travail du ramasse-miettes et garantir une suppression rapide de l'objet en mémoire.

Les évènements

L’une des sources classiques d’une forte consommation de la mémoire vient de l’utilisation des écouteurs d’évènements. Lorsqu’un objet écouteur d’évènements est enregistré auprès d’un objet distributeur d’évènements, une référence est créée au niveau du moteur d’exécution qui lie l’objet écouteur à l’objet distributeur.

Il est possible de spécifier le type de référence que nous souhaitons mettre en place entre l'objet écouteur et le distributeur : référence forte ou référence faible. Par défaut, la méthode addEventListener() spécifie une référence forte.

Pour les exemples suivants, nous créons deux classes : une classe distributeur d’évènements (Dispatcher) et une classe écouteur d’évènements (Listener).

La classe Dispatcher est une classe qui se contente d’étendre la classe EventDispatcher :

package {
  import flash.events.EventDispatcher;
 
  public class Dispatcher extends EventDispatcher {
 
    public function Dispatcher() {
      super();
    }
  }
}

La classe Listener est une classe dont les instances feront office d’objets écouteurs d’évènements. Elle contient deux méthodes permettant de définir l’objet comme écouteur d’évènements :

  • addListener() qui spécifie une référence forte
  • addListenerWeak() qui spécifie une référence faible
package {
 
  import flash.events.Event;
 
  public class Listener {
 
    public function Listener() {
 
    }
 
    public function addListener(dispatcher:Dispatcher):void {
      dispatcher.addEventListener("event", _handler, false, 0, false) ;
    }
 
    public function addListenerWeak(dispatcher:Dispatcher):void {
      dispatcher.addEventListener("event", _handler, false, 0, true) ;
    }
 
    private function _handler(event:Event):void {
 
    }
  }
}

Référence forte

Par défaut, la méthode addEventListener() spécifie une référence forte. Voyons comment se comporte en mémoire un objet écouteur avec un référence forte

Exemple
var dispatcher:Dispatcher = new Dispatcher() ;
 
var listener:Listener = new Listener() ;
listener.addListener(dispatcher) ;
listener = null ;

Dans cet exemple, nous instancions un Dispatcher et un Listener. Nous définissons l’objet Listener comme écouteur de l’objet Dispatcher. La référence à l’objet Dispatcher est conservée, tandis que la référence à l’objet Listener est supprimée. Dans ce cas, l’objet Listener ne sera jamais supprimé de la mémoire car il persiste la référence entre l’objet écouteur et l’objet distributeur.

Maintenant, supprimons la référence à l'objet distributeur

Exemple
var dispatcher:Dispatcher = new Dispatcher() ;
 
var listener:Listener = new Listener() ;
listener.addListener(dispatcher) ;
listener = null ;
 
dispatcher = null ;

Une fois la référence à l'objet distributeur supprimée, le ramasse-miettes va pouvoir supprimer de la mémoire l'objet distributeur supprimant de fait la référence à l'objet écouteur qui pourra à son tour être supprimé de la mémoire. Mais attention, comme dans le cas des références circulaires, la suppression de l'objet écouteur de la mémoire peut prendre un peu de temps.

Les seuls moyens pour supprimer un objet écouteur lié avec une référence forte sont :

  • comme nous venons de le faire, supprimer les références à l'objet distributeur pour qu'il soit supprimé de la mémoire avec le risque que l'objet écouteur reste un certain temps en mémoire le temps que le ramasse-miettes se déclenche et le détecte
  • supprimer l'enregistrement de l'objet écouteur auprès de l'objet distributeur avec la méthode removeEventListener() de l'interface IEventDispatcher. Utiliser cette méthode garantit que le ramasses-miettes supprimera rapidement l'objet écouteur de la mémoire.

Référence faible

Pour spécifier une référence faible lors de l'enregistrement d'un objet écouteur auprès d'un objet distributeur, il faut définir le paramètre useWeakReference de la méthode addEventListener() à true.

Lorsqu'un objet écouteur est enregistré avec une référence faible, le ramasse-miettes ne prend pas en compte cette référence pour déterminer si un objet peut-être supprimé de la mémoire ou non.

Exemple
var dispatcher:Dispatcher = new Dispatcher() ;
 
var listener:Listener = new Listener() ;
listener.addListenerWeak(dispatcher) ;
listener = null ;

Dans cet exemple, nous instancions un Dispatcher et un Listener. Nous définissons l’objet Listener comme écouteur de l’objet Dispatcher avec une référence faible. La référence à l’objet Dispatcher est conservée, tandis que la référence à l’objet Listener est supprimée.

Dans ce cas, l’objet Listener est supprimé de la mémoire puisque la seule référence qui existe encore, susceptible de le maintenir en mémoire, est celle en tant qu'écouteur d'évènement. Or, la référence étant faible, elle n'est pas prise en compte.

Démonstration

Lancez en mode Profiler l’application Events.

Au démarrage, vous devriez avoir une référence de l'objet Dispatcher. Il s'agit de l'instance du distributeur instanciée à l'initialisation de l'application et référencée localement. Nous allons tester tous les cas de figure. À chaque fois, aucune référence à l'objet Listener instanciée n'est conservée localement à l'application. Vérifiez bien les colonnes Occurrences cumulées et Occurrences du Profiler pour vérifier ce qui se passe en mémoire.

Référence forte sur instance locale

Cliquez sur le bouton Référence forte sur instance locale de Dispatcher.

Un objet écouteur (Listener) est enregistré avec référence forte auprès de l'objet distributeur référencé localement.

La référence étant forte, vous constater qu'une occurrence de Listener est présente en mémoire.

Référence faible sur instance locale

Cliquez sur le bouton Référence faible sur instance locale de Dispatcher.

Un objet écouteur (Listener) est enregistré avec référence faible auprès de l'objet distributeur référencé localement.

La référence étant faible, vous constatez qu'une occurrence de Listener a été instanciée, mais qu'aucune occurrence de Listener supplémentaire n'est présente en mémoire.

Référence forte sur nouvelle instance

Cliquez sur le bouton Référence forte sur nouvelle instance de Dispatcher.

Un objet écouteur (Listener) est enregistré avec référence forte auprès d'une nouvelle instance de Dispatcher. Aucune référence à cette nouvelle instance n'est conservée localement.

La référence est forte mais l'instance de Dispatcher n'est pas conservée. Logiquement, vous constatez qu'une occurrence de Listener et une occurrence de Dispatcher ont été instanciées, mais qu'aucune n'a été conservée en mémoire.

Référence faible sur nouvelle instance

Cliquez sur le bouton Référence faible sur nouvelle instance de Dispatcher.

Un objet écouteur (Listener) est enregistré avec référence faible auprès d'une nouvelle instance de Dispatcher. Aucune référence à cette nouvelle instance n'est conservée localement.

La référence est faible et l'instance de Dispatcher n'est pas conservée. Logiquement, vous constatez qu'une occurrence de Listener et une occurrence de Dispatcher ont été instanciées, mais qu'aucune n'a été conservée en mémoire.

Supprimer l'instance locale de Dispatcher

Cliquez sur le bouton Suppression de la référence locale de Dispacther.

La référence locale à l'instance de Dispatcher est supprimée. L'objet Dispatcher est donc supprimé de la mémoire, vous ne devriez plus voir d'instance de Dispatcher en mémoire. Cette objet distributeur supprimé, tous les objets écouteur enregistrés avec référence forte auprès de ce Dispatcher devraient être supprimées. Mais la détection de ces objets écouteur par le ramasse-miettes n'intervient pas immédiatement, ils restent en mémoire.

Cliquez sur le bouton Lancer la récupération de place.

Vous forcer ainsi le ramasse-miettes à recalculer les références et à supprimer de la mémoire les objets inutiles. Les objets écouteurs restants sont bien détectés et supprimés de la mémoire.

Cas particuliers

Certains cas particuliers peuvent provoquer des comportements imprévus. C'est le cas des distributeurs d'évènements qui sont référencés par le moteur d'exécution. Prenons comme exemple la classe flash.utils.Timer. Lorsque la méthode start() est appelée sur une instance de Timer, elle se retrouve référencée par le moteur d'exécution et si on supprime toutes les références à l'instance de Timer, celle-ce restera malgré tout en mémoire. Donc, si un objet écouteur est enregistré auprès de l'instance de Timer, elle restera en mémoire.

Exemple
package {
 
  import flash.events.TimerEvent;
  import flash.utils.Timer;
 
  public class TimerListener {
 
    public function TimerListener()	{
      var timerTest:Timer = new Timer(200, 0) ;
      timerTest.addEventListener(TimerEvent.TIMER, timerHandler) ;
      timerTest.start() ;
    }
 
    public function timerHandler(event:TimerEvent):void {
      trace('TimerListener.timerHandler()') ;
    }
  }
}

Si vous instanciez cette classe, elle restera en mémoire quoiqu'il arrive car l'occurrence serait enregistrée en tant qu'objet écouteur du Timer qui serait lui-même enregistré auprès du moteur d'exécution.

En revanche, si l'enregistrement de l'objet écouteur était faite avec une référence faible, l'occurrence de TimerListener serait supprimée de la mémoire, mais pas le Timer !

Si vous tester ce code en mode Profil, n'oubliez pas de supprimer le filtre sur les packages flash dans le Profiler pour pouvoir suivre le nombre d'instances de Timer.

Conclusion

La gestion des évènements est source d'une consommation excessive de mémoire et d'exécution de code non voulu. Il faut donc bien penser à nettoyer les références aux objets écouteurs et les enregistrements d'objets écouteurs auprès d'objets distributeur.

Dans la majorité des cas, une référence faible est suffisante. Si vous contrôlez les objets écouteurs, c'est à dire que vous maîtrisez les références à ces objets, des références faibles vous garantirons que les objets écouteurs seront supprimés de la mémoire.

Si vous avez besoin de références fortes, pensez à bien supprimer les écouteurs dès qu'il n'y a plus rien à écouter. Là encore, des méthodes release() peuvent être utiles pour bien nettoyer les objets écouteurs avant de supprimer toutes les références.

Éviter les déplacement mémoire

Recyclage

Autres

Dictionnary