Les modules Flex et les fuites mémoires
Par Olivier Bugalotto le jeudi, octobre 22 2009, 21:57 - Flex - Lien permanent
Les modules flex sont très controversés concernant leurs implications sur les fuites mémoires qu'ils peuvent engendrer. A ce jour, sauf erreur de notre part, il n'existe pas de solution au problème sur le net et pourtant, si on y regarde d'un peu plus prêt, il y en a une...
[Le code source de cet article est disponible en téléchargement : Les modules.zip
Mais avant de nous attaquer au problème voici une petite définition des modules, bien entendu pour ceux qui la connaissent vous pouvez passer directement au paragraphe suivant.
Les modules
Un module représente une fonctionnalité ou un ensemble de fonctionnalités regroupées de manière logique et pouvant être partagé par plusieurs applications où propre à une seule si par exemple on décide de diviser la charge de travail en équipe. Le module n'est pas autonome, il a besoin d'un environnement d'exécution: ici d'une application flex.
Dans flex les modules se présentent sous la forme d'un composant MXML qui se charge et se décharge dynamiquement par l'application principale que nous appellerons pour le reste de cet article hôte.
Les modules ne se limitent pas uniquement à du visuel, ils peuvent être aussi un ensemble de classes et de fonctions : une librairie externe en quelque sorte.
Les modules dans Flex
D'un point de vue du code, les modules sont des composants MXML qui héritent du composant Module
où une classe héritant de BaseModule dans le cas d'une libraire dynamique contenant des classes et/ou fonctions.
Le domaine d'application
Nous savons qu'un module est un swf externe que l'on charge dynamiquement, ce swf possède ce qu'on appelle un domaine d'application dans lequel se trouve ses définitions de classes. Ce domaine permet de définir le niveau de sécurité de son contenu pour un éventuel partage entre applications. Il existe un document qui en parle très bien Utilisation de la classe ApplicationDomain
Le rôle de SystemManager dans les modules
Le SystemManager est le chef-orchestre d'une application : le lanceur applicatif, celui qui contient et affiche l'application pour être plus précis. Il ne fait pas que ça bien entendu, c'est lui par exemple qui s'occupe d'affiche les popups, les tooltips et les curseurs permettant à ces derniers d'être toujours au dessus de votre application... mais surtout c'est lui le responsable du problème de fuite de mémoires avec les modules.
Regardons cela de plus prêt à travers une application hôte qui charge deux modules et nous constaterons les fuites de mémoire grâce au profileur.
Voici la structure de notre application :

Maintenant observons la du point de vue des SystemManager :

Nous remarquons que les systemManagers des modules chargés sont référencés dans celui de l'application hôte. Ces références sont automatiquement détruites après leurs déchargement SAUF qu'il reste toujours en mémoire au moins une instance d'un module.
Pour le vérifier lançons l'application en mode profilage et sélectionnons uniquement les options pour observer la mémoire :

Maintenant ajoutons plusieurs contacts à notre application et retournons dans le profileur.

Pour observer le bon fonctionnement du garbage collector, nous allons effectuer un cliché mémoire. Le cliché mémoire est la représentation mémoire des objets présents à un instant T. Lorsque nous effectuons ce cliché, le profileur déclenche en même temps le passage du garbage collector, nous permettant de vérifier son bon fonctionnement en détruisant les objets.

Nous pouvons donc imaginer le pire lorsque votre application utilise des modules plus complexes que ceux que nous vous proposons. Nous avons pu observer sur le terrain de nombreuses applications industrielles montées en charge pouvant atteindre un plafond mémoire impensable, le premier réflexe est de penser que les modules sont mal conçus et de les écarter du développement.
La solution au problème de fuites mémoires
Si nous observons le code source de la classe SystemManager à la ligne 208 :
/** * @private * The last SystemManager instance loaded as child app domains */ mx_internal static var lastSystemManager:SystemManager;
et bien, le systemManager de l'hôte conserve la référence du systemManager du dernier module chargé, hey hey 
La solution est simplement de marquer cette référence à null pour qu'elle soit bien détruite par le garbage collector.
Nous devons le faire lorsque le module est déchargé en écoutant l'événement unload sur ModuleLoader.
Ajoutons le code suivant à notre application hôte :
<mx:ModuleLoader id="loader" url="FormModule.swf" ready="onReady()" unload="onUnload()" /> <mx:Script> <![CDATA[ ... import mx.core.mx_internal; use namespace mx_internal; ... private function onUnload():void { SystemManager.lastSystemManager = null; } ]]> </mx:Script>
Testons le code à présent en ajoutant des contacts, en affichant plusieurs fois les modules pour effectuer une montée en charge et prenons ensuite un cliché de mémoire et là : magique, IL NE RESTE PLUS QUE LE MODULE AFFICHE EN MEMOIRE !

Il y a bien entendu d'autres facteurs en prendre en compte concernant la gestion mémoire dont vous pouvez trouver les resources sur le blog de Grant Skinner :
Resource management pt 1
Resource management pt 2
Resource management pt 3
Weakly Referenced Listeners
Conclusion
Le rôle des experts est de dénicher ce genre d'information qui peut parfois faire basculer le choix que doivent prendre les entreprises sur une technologie telle que Flex. Il est dommage que les modules soient parfois la cause de l'abandon de l'utilisation du framework Flex pour une autre technologie.
Compléments d'informations
Nous avons oublié de faire mention dans l'article qu'il arrive parfois que l'événement unload ne soit pas diffusé. Il suffit pour cela d'utiliser d'autres mécanismes pour marquer la référence SystemManager. lastSystemManager à null.

Commentaires
c'est un peu plus compliqué que ca
voir les articles suivant
http://jvalentino.blogspot.com/2009...
http://blogs.adobe.com/aharui/2009/...
http://blogs.adobe.com/aharui/2009/...
Tout à fait, c'est un tout c'est bien ce que je précise dans l'article il faut aussi prendre en considération d'autres mécanismes.
D'ailleurs ils parlent du focus mais ils ne disent pas que le fautif c'est focusPane et ceci à cause des UITextFields puisque si tu effectues un test avec le profileur, on remarques entre deux clichés mémoires que le focusManager de l'hôte conserve la dernière référence du parent contenant un UITextField du module déchargé.
J'en rajoute une couche concernant les UITextFields et le focusPane, on remarque que les classes de type xxx_FlexModuleFactory possèdent aussi cette référence à focusPane créant une double références sur l'application hôte et les modules...
Salut,
Il semblerai que dans l'API de Flex 4 (beta 2) la propriété lastSystemManager a été remplacé par allSystemManagers et j'ai remarqué aussi qu'il y a plus de fuite avec le test que tu as fait.
La propriété
allSystemManagersexiste aussi en Flex 3 simplement dans la version flex 4 ils ont retiréslastSystemManagerà cause de ce problème de référence. Concernant Flex 4, on ne peut pas vraiment en parler puisqu'il n'est pas encore sur le marché et personne ne codera avec avant un petit moment. Non, ici le sujet est flex 3 c'est du concret et utilisé en industrie.Désolé de répondre par petit bout mais je fais deux choses à la fois
Zwetan: concernant par exemple la déclaration de styles avec
<mx:Style>dans le module il n'y a pas de quoi être inquiété puisqu'il suffit de mettre en place un mécanisme dans ce dernier pour les retirer au moment de son déchargement. Le véritable problème vient de la génération faite par le compilateur flex et surtout l'incapacité (enfin presque) de pouvoir prendre le pas sur les xxx_FlexModuleFactory ou encore xxx_SystemManager sans patcher le framework...Bon et bien, les liens que tu m'as donné zwetan ne m'apprend rien de plus que ce que je sais déjà
Il est clair que le fait de charger un module dans une application, dans le cas ou le domaine d'application est partagé, augmente la mémoire puisque le domaine d'application se voit ajouter de nouvelles définition de classes, mais c'est surtout sur le contrôle des instances que j'utilise en autre cette première solution qui doit être combiné à d'autres mécanismes comme le problème des UITextFields cités plus haut ou encore comme les popus affichées depuis un module... bien entendu, on ne pourra pas solutionner l'augmentation résiduelle due à l'allocation mémoire entre l'OS et le flash player...
Ah voici donc le fameux article sur les modules
Que de souvenirs de revoir ces écrans de profilage sur les modules
"Concernant Flex 4, on ne peut pas vraiment en parler puisqu'il n'est pas encore sur le marché et personne ne codera avec avant un petit moment."
-> Pas d'accord. Les projets Flex 4 sont et vont être de plus en plus nombreux, je peux en parler... Si qq a des bonnes pratiques sur le sujet en flex 4, je suis preneur !
Il faut savoir que le passage d'une version à une autre est très long, surtout aux niveaux des grosses entreprises, un de mes derniers clients vient à peine de passer à la version 3.2, il y a beaucoup d'entreprises que ne savent même pas qu'il y a une version 3.4 du SDK
... alors la version 4 faut attendre encore un peu, il est clair que les petites entreprises, parce les équipes sont plus petits, font plus rapidement le passage. Mais c'est toujours un grand risque à vouloir toujours la dernière version d'une technologie, il faut d'abord attendre et avoir du retour. Par contre il faut s'y préparer, c'est pour cela qu'il faut faire de la veille.
@Donald et @Keira
J'ai eu une formation par Olivier et je confirme, c'est un Flex Guru.
Il est impressionnant.