POURQUOI CE BLOG, POUR QUI ?


POURQUOI CE BLOG, POUR QUI ?
Ce Blog s'adresse à tous ceux qui sont passionnés par les sciences informatiques , Professionnels,Etudiants,Amateurs ...
Les sujets exposés dans la suite se rapporteront essentiellement sur l'analyse informatique,la programmation,le développement ainsi que à l'architecture IT.
QUI SUIS JE ?
Je suis Kangulungu Lubilanji, Consultant-Freelance sur les technologies .NET,C#,ASP.NET ... Contactez moi pour plus d'informations.

La Programmation OO: Chapitre 9. Vie et mort des objets

Chapitre 9.  Vie et mort des objets
Ce chapitre a pour objectif de présenter les différentes manières d'effacer les objets de la mémoire pendant qu'un programme s’exécute.Nous verrons comment le langage C# traite ce cas.


Un rappel sur la mémoire RAM, un programme, pour qu'il s'exécute, nécessite, avant tout de l'espace mémoire, lors de son exécution il faudra pouvoir stocker, et les objets et les méthodes.
La mémoire RAM, ou vive ou encore centrale sert à cela. Les différents programmes auront une zone mémoire propre qui leur sera réservée, comme un casier dans un vestiaire et qu'ils utiliseront exclusivement durant leur exécution.
Pour qu'un programme tourne vite, il est idéal que toutes les données et instructions qu'il manipule puissent être stockées dans la RAM, sinon le programme rame ... Ce que l'on ne peut installer dans la RAM pourra, en dernier recours, être stocké sur le disque dur (on parle de mémoire virtuelle), provoquant en cela un effondrement des performances, vu que celui-ci prend pour l'extraction des données un million de fois plus de temps que la mémoire RAM. Cette mémoire-là est donc extrêmement précieuses, vu sa sophistication et son prix, elle est non extensible à l'infini.
La mémoire RAM brûle les poches des développeurs d'applications. De ce fait, une des préoccupations des programmeurs d'antan était d'économiser les ressources de l'ordinateur lors du développement des applications, le temps calcul et la mémoire. Aujourd'hui, l'existence même de pratique informatique comme OO permet de s'affranchir quelque peu de ce souci d'optimisation, pour le remplacer graduellement  par un souci de simplicité, clarté, adaptabilité et facilité de maintenance. Ce que l'on gagne d'un côté, on le perd ailleurs. En effet, la pratique de l'OO ne regarde pas trop à la dépense, et ce à plusieurs titre.


L'OO coûte cher en mémoire, l'objet, déjà lui-même, est généralement plus coûteux en mémoire que les simples variables int, char ou double de type prédéfini. Il pousse à la dépense. De plus, rappelez-vous le « new » qui vous permet d'allouer de la mémoire pendant le déroulement de l'exécution programme, et ce n'importe où. Alors pourquoi s'en priver ? A la différence d'autres langages, tout l'espace mémoire utilisé pendant l'exécution du programme n'est pas déterminé à l'avance, ni optimisé par l'étape de compilation.
Bien que la pratique de l'OO soit une grande consommatrice de mémoire RAM, et que cette ressource extrêmement précieuse, toute pratique visant à économiser cette ressource pendant l'exécution du programme est plus qu’appréciable.


Qui se ressemble s'assemble : le principe de localité, un autre point capital dans la gestion de la mémoire est qu'il est important que les instructions données qui seront lues et exécutées à la suite se trouvent localisées dans la même zone mémoire. La raison en est l'existence aujourd'hui dans les ordinateurs d'un système de mémoire hiérarchisé (telle la mémoire cache), ou de bloc de données et d'instructions sont extraits d'un premier niveau lent, pour être installés dans un second niveau plus rapide. Cela permet, lors de l'exécution du programme, d'extraire, le plus souvent possible, les données nécessaires à cette exécution hors du premier niveau.
Suite aux ratés, quand ce qui est requis pour la poursuite de l'exécution ne se trouve plus dans le niveau rapide, il est nécessaire d'extirper à nouveau un bloc de données du niveau lent, en ralentissant considérablement l'exécution.On verra qu’afin de diminuer les effets néfastes d'une telle répartition des objets, des systèmes automatiques cherchent à compacter au mieux la zone mémoire occupée par ces objets et à les maintenir le plus possible dans la mémoire cache.


Les objets intérimaires, si, au fur et à mesure de son exécution, le programme rajoute de nouveaux objets dans la mémoire, il serait commode, dans le même temps, de se débarrasser de ceux devenus inutiles et encombrants. Mais quand un objet devient-il inutile ? Tout d'abord, quand le rôle qu'il doit jouer est par essence temporaire. Par exemple, quand il permet à des structures de données de se transformer en passant par lui, mais n'est plus requis une fois les structures finales obtenues.


Mémoire Pile, ce qui est vrai pour des objets l'est et l'a toujours été de n'importe quelle variable informatique, qui n'aurait de rôle à jouer que pendant un cours laps de temps et l'occasion d'une fonctionnalité bien précise. Considérons le programme suivant dans lequel une méthode s'occupe de calculer, à partir de trois arguments reçus, les deux bases et la hauteur de la surface d'un trapèze. Durant l'exécution  de cette méthode cinq variables intermédiaires vont se créer et disparaîtrons aussitôt l'exécution terminée. D'abord lors de l'appel de la méthode, trois variables nouvelles seront nécessaires pour stocker les trois dimensions du trapèze. Si ces variables existent déjà à l'extérieur de la méthode, elle seront purement et simplement dupliquées, pour être installées dans ces variables intermédiaires. Ensuite pendant l'exécution de la méthode, une quatrième variable intermédiaire, surface, est crée, qui permettra de stocker le résultat jusqu'à la fin  de cette exécution. Si la surface est calculable, une cinquième et dernière variable intermédiaire : sommeBases, permettra de stocker temporairement  une valeur intermédiaire, dont l'usage un peu forcé ici, permet également une meilleure lisibilité du programme et une algorithmique plus sûre, car décomposée en une succession d'étapes plus simple. 

Il vous paraîtra évident qu'une fois la méthode achevée, toutes ces variables doivent disparaître de la mémoire pour laisser la place à d'autre et sans qu'on ne les y invite. Ces variables sont stockées, comme indiqué dans la figure, dans une mémoire « pile » (et qui ne s'use que si l'on s'en sert). Le principe de fonctionnement de cette mémoire est dit « FIFO » (dernier dedans premier dehors). Dans tous le cas un bloc d'instructions, encadré par les accolades, délimite également la portée des variables.
Dans une informatique séquentielle traditionnelle, un bloc d'instruction ne sera jamais interrompu. Quand un bloc se termine, les variables du dessus de la pile disparaissent tout naturellement (car elles sont utilisables qu'à l'intérieur de ce bloc), alors que lorsqu'un bloc s'entame, les nouvelles variables s'installent au-dessus de la pile. Aucune recherche sophistiquée n'est nécessaire pour retrouver les variables à supprimer. Ce seront toujours les dernières à s'être installées sur la pile. De même, de cette façon, aucun gaspillage n'est possible, et aucune zone de mémoire inoccupée peut se trouver, perdu au milieu de zones occupées.
Gestion par mémoire pile, ce système de gestion de mémoire est donc extrêmement ingénieux, car il est fondamentalement économe, gère de façon adéquate le temps de vie des variables intermédiaires par leur participation dans des fonctionnalités précises, garde rassemblées les variables qui agissent de concert, et synchronise le mécanisme d'empilement et de dépilement  des variables avec l’emboîtement des méthodes.

Structure en C#, les objets issus d'une structure sont traités directement par valeur, dans la mémoire pile, et sans référent intermédiaire. Ils le sont comme n'importe quelle variable de type prédéfini. Les structures sont utilisées en priorité pour des objets que l'on veut et que l'on sait temporaire. Un constructeur par défaut y est prévu et donc on  ne peut le surcharger en définissant explicitement un  autre. Plus important encore, les structures ne peuvent hériter entre elles, bien qu'elles héritent toutes de la classe Objet. En revanche, elles peuvent implémenter des interfaces. Tout cela s'explique aisément lorsqu'on sait que les structures sont exploitées par valeur et non par référent, et que les mécanismes d'héritage et de polymorphisme sont plus faciles à réaliser pour des objets uniquement adressés par leur référent. Il est plus facile de s'échanger les référents que les objets.  
Disparaître de la mémoire comme de la vie réelle, ce mode de gestion de la mémoire pile est intimement lié à une organisation procédurale de la programmation, où le programme est décomposé en procédures ou en blocs imbriqués, lesquels nécessitent, uniquement pendant leur déroulement, un ensemble de variables, qui seront éliminées à la fin. Nous avons vu que la programmation OO se détache de cette vision, en privilégiant les objets de leur participation à certaines fonctions précises. L'esprit de l'OO est qu'un objet devient encombrant si, dans le scénario même que reproduit le programme, l'objet réel, que son modèle informatique « interprète », disparaît tout autant de la réalité. Dans le petit écosystème vu précédemment , la proie disparaît quand elle se fait manger par le prédateur.
A chaque fois, l'objet représenté disparaît, tant et si bien que son élimination de la mémoire est même souhaitée, pour permettre à un un nouvel objet d'exister et prendre sa place. Il est bien plus difficile d'organiser cette gestion de la mémoire par un mécanisme de pile car, une fois l'objet créé, son temps de vie peut transcender plusieurs blocs fonctionnels, pour finalement disparaître, éventuellement de manière conditionnelle, dans l'un d'entre eux (et pas du tout automatiquement dans le bloc où il fut créé). L'OO permet aux objets de vivre bien plus longtemps, et, surtout, rend leur élimination indépendante des fonctions qui les manipule, mais plus dépendante du scénario qui se déroule, aussi inattendu soit-il.

La vie des objets indépendante de ce qu'il font, l'orienté objet, se détachant d'une vision procédurale de la programmation, tend à rendre indépendante la gestion mémoire occupée par les objets de leur participation dans l'une ou l'autre opération. Cette nouvelle gestion mémoire résultera d'un suivi des différentes transformations subies par l'objet et sera, soit laissée à la responsabilité du programmeur, soit automatisée. 
La mémoire à des fuites, il est très fréquent, dans les langages ou vous êtes responsables de la gestion mémoire de laisser traîner des objets devenus inaccessibles, donc parfaitement encombrants. On parle alors de « fuite mémoire ». Dans ce scénario, les objets existent encore, ils sont devenus hors de portée. Ils ralentissent le code et font chuter les performances, mais n'occasionnent rien de totalement imprévisible.
En C# : la chasse au gaspi, en C# l'idée est que, dès qu'un objet n'est plus utile dès lors qu'il ne peut plus être référencé. Un objet devenu inaccessible ne demande qu'à vous restituer la place qu'il occupait. L'idéal serait donc de réussir à vous débarrasser, à votre insu (mais tout à votre bénéfice), pendant l'exécution du programme, des objets devenus encombrants. Une manière simple est de rajouter, comme attribut caché de chaque objet, un compteur de référents, et de supprimer tout objet dès que ce compteur devient nul. En effet, un objet débarrassé de tout référent est inaccessible, donc inutilisable, et donc bon à jeter.


Le garbage collector ou ramasse-miettes, c'est un mécanisme puissant en C#, qui permet de libérer le programmeur du souci de la suppression explicite des objets encombrants. Il s'effectue au prix d'une exploration continue de la mémoire, simultanée à l'exécution du programme, à la recherche des compteurs de référents nuls (un compteur de référent existe pour chaque objet) et des structures relationnelles cycliques. Une manière classique de l'accélérer est de limiter son exploitation aux objets les plus récemment créés. Ce ramasse-miettes est manipulable de l'intérieur du programme et peut être, de fait appelé ou simplement désactivé.

Aucun commentaire: