Article de reference

Fuite de mémoire

En informatique , une fuite de mémoire est un type de fuite de ressources qui se produit lorsqu'un programme informatique gère de manière incorrecte les allocations de mémoire d...

En informatique , une fuite de mémoire est un type de fuite de ressources qui se produit lorsqu'un programme informatique gère de manière incorrecte les allocations de mémoire de telle sorte que la mémoire qui n'est plus nécessaire n'est pas libérée. Une fuite de mémoire peut également se produire lorsqu'un objet est stocké en mémoire mais n'est pas accessible par le code en cours d'exécution (c'est- à-dire une mémoire inaccessible ). Une fuite de mémoire présente des symptômes similaires à un certain nombre d'autres problèmes et ne peut généralement être diagnostiquée que par un programmeur ayant accès au code source du programme.

Un concept connexe est la « fuite d'espace », qui se produit lorsqu'un programme consomme une quantité excessive de mémoire mais finit par la libérer.

Parce qu’elles peuvent épuiser la mémoire système disponible lors de l’exécution d’une application, les fuites de mémoire sont souvent la cause ou un facteur contribuant au vieillissement des logiciels .

Conséquences

Une fuite de mémoire réduit les performances de l'ordinateur en réduisant la quantité de mémoire disponible. Une fuite de mémoire peut entraîner une augmentation de l'utilisation de la mémoire, du temps d'exécution des performances et peut avoir un impact négatif sur l'expérience utilisateur. Finalement, dans le pire des cas, une trop grande quantité de mémoire disponible peut être allouée et tout ou partie du système ou de l'appareil cesse de fonctionner correctement, l'application échoue ou le système ralentit considérablement en raison d' un thrashing .

Les fuites de mémoire peuvent ne pas être graves, ni même détectables par des moyens normaux. Dans les systèmes d'exploitation modernes, la mémoire normale utilisée par une application est libérée lorsque l'application se termine. Cela signifie qu'une fuite de mémoire dans un programme qui ne s'exécute que pendant une courte période peut passer inaperçue et est rarement grave.

Les fuites les plus graves sont celles-ci :

  • lorsqu'un programme s'exécute pendant une longue période et consomme de la mémoire supplémentaire au fil du temps, comme les tâches en arrière-plan sur les serveurs, et en particulier dans les systèmes embarqués qui peuvent rester en cours d'exécution pendant de nombreuses années
  • où une nouvelle mémoire est allouée fréquemment pour des tâches ponctuelles, comme lors du rendu des images d'un jeu informatique ou d'une vidéo animée
  • où un programme peut demander de la mémoire, telle que la mémoire partagée , qui n'est pas libérée, même lorsque le programme se termine
  • lorsque la mémoire est très limitée, comme dans un système embarqué ou un appareil portable, ou lorsque le programme nécessite une très grande quantité de mémoire pour commencer, laissant peu de marge pour les fuites
  • lorsqu'une fuite se produit dans le système d'exploitation ou le gestionnaire de mémoire
  • lorsqu'un pilote de périphérique système provoque une fuite
  • exécuté sur un système d'exploitation qui ne libère pas automatiquement la mémoire à la fin du programme.

Un exemple de fuite de mémoire

L'exemple suivant, écrit en pseudo-code , a pour but de montrer comment une fuite de mémoire peut se produire, et ses effets, sans nécessiter de connaissances en programmation. Le programme dans ce cas fait partie d'un logiciel très simple conçu pour contrôler un ascenseur . Cette partie du programme est exécutée chaque fois qu'une personne à l'intérieur de l'ascenseur appuie sur le bouton d'un étage.

Lorsqu'un bouton est appuyé : Obtenez de la mémoire, qui sera utilisée pour mémoriser le numéro d'étage Mettre le numéro d'étage en mémoire Sommes-nous déjà à l’étage cible ? Si c'est le cas, nous n'avons rien à faire : fini Sinon: Attendez que l'ascenseur soit inactif Aller à l'étage souhaité Libérez la mémoire que nous avons utilisée pour nous souvenir du numéro d'étage 

La fuite de mémoire se produit si le numéro d'étage demandé correspond au même étage que celui où se trouve l'ascenseur ; la condition de libération de la mémoire est ignorée. Chaque fois que ce cas se produit, davantage de mémoire est divulguée.

Des cas comme celui-ci n'ont généralement pas d'effets immédiats. Les gens n'appuient pas souvent sur le bouton de l'étage où ils se trouvent et, de toute façon, l'ascenseur peut avoir suffisamment de mémoire de réserve pour que cela se produise des centaines ou des milliers de fois. Cependant, l'ascenseur finira par manquer de mémoire. Cela peut prendre des mois ou des années, et le problème pourrait donc ne pas être détecté malgré des tests approfondis.

Les conséquences seraient désagréables ; au minimum, l'ascenseur cesserait de répondre aux demandes de changement d'étage (par exemple lorsqu'on tente d'appeler l'ascenseur ou lorsque quelqu'un est à l'intérieur et appuie sur les boutons d'étage). Si d'autres parties du programme ont besoin de mémoire (une partie affectée à l'ouverture et à la fermeture de la porte, par exemple), alors personne ne pourrait entrer, et si quelqu'un se trouve à l'intérieur, il sera piégé (en supposant que les portes ne puissent pas être ouvertes manuellement).

La fuite de mémoire dure jusqu'à ce que le système soit réinitialisé. Par exemple, si l'ascenseur était éteint ou en cas de panne de courant, le programme s'arrêterait de fonctionner. Lorsque l'alimentation serait rétablie, le programme redémarrerait et toute la mémoire serait à nouveau disponible, mais le lent processus de fuite de mémoire redémarrerait en même temps que le programme, ce qui finirait par compromettre le bon fonctionnement du système.

La fuite dans l'exemple ci-dessus peut être corrigée en plaçant l'opération « release » en dehors du conditionnel :

Lorsqu'un bouton est appuyé : Obtenez de la mémoire, qui sera utilisée pour mémoriser le numéro d'étage Mettre le numéro d'étage en mémoire Sommes-nous déjà à l’étage cible ? Sinon: Attendez que l'ascenseur soit inactif Aller à l'étage souhaité Libérez la mémoire que nous avons utilisée pour nous souvenir du numéro d'étage 

Problèmes de programmation

Les fuites de mémoire sont une erreur courante en programmation, en particulier lors de l'utilisation de langages qui n'ont pas de récupération de mémoire automatique intégrée , tels que C et C++ . En général, une fuite de mémoire se produit parce que la mémoire allouée dynamiquement est devenue inaccessible . La prévalence des bogues de fuite de mémoire a conduit au développement d'un certain nombre d' outils de débogage pour détecter la mémoire inaccessible. BoundsChecker , Deleaker , Memory Validator, IBM Rational Purify , Valgrind , Parasoft Insure++ , Dr. Memory et memwatch sont quelques-uns des débogueurs de mémoire les plus populaires pour les programmes C et C++. Des capacités de récupération de mémoire « conservatrices » peuvent être ajoutées à tout langage de programmation qui en manque en tant que fonctionnalité intégrée, et des bibliothèques pour le faire sont disponibles pour les programmes C et C++. Un collecteur conservateur trouve et récupère la plupart, mais pas la totalité, de la mémoire inaccessible.

Bien que le gestionnaire de mémoire puisse récupérer la mémoire inaccessible, il ne peut pas libérer la mémoire qui est toujours accessible et donc potentiellement encore utile. Les gestionnaires de mémoire modernes fournissent donc des techniques permettant aux programmeurs de marquer sémantiquement la mémoire avec différents niveaux d'utilité, qui correspondent à différents niveaux d' accessibilité . Le gestionnaire de mémoire ne libère pas un objet fortement accessible. Un objet est fortement accessible s'il est accessible soit directement par une référence forte , soit indirectement par une chaîne de références fortes. (Une référence forte est une référence qui, contrairement à une référence faible , empêche un objet d'être récupéré par le garbage collector.) Pour éviter cela, le développeur est responsable du nettoyage des références après utilisation, généralement en définissant la référence sur null une fois qu'elle n'est plus nécessaire et, si nécessaire, en désenregistrant tous les écouteurs d'événements qui maintiennent des références fortes à l'objet.

En général, la gestion automatique de la mémoire est plus robuste et plus pratique pour les développeurs, car ils n'ont pas besoin d'implémenter des routines de libération, ni de se soucier de la séquence dans laquelle le nettoyage est effectué, ni de se soucier de savoir si un objet est toujours référencé ou non. Il est plus facile pour un programmeur de savoir quand une référence n'est plus nécessaire que de savoir quand un objet n'est plus référencé. Cependant, la gestion automatique de la mémoire peut imposer une surcharge de performances et n'élimine pas toutes les erreurs de programmation qui provoquent des fuites de mémoire.

RAII

L'acquisition de ressources par initialisation (RAII) est une approche du problème couramment utilisée en C++ , D et Ada . Elle consiste à associer des objets de portée aux ressources acquises et à libérer automatiquement les ressources une fois que les objets sont hors de portée. Contrairement au garbage collection, RAII a l'avantage de savoir quand les objets existent et quand ils n'existent pas. Comparez les exemples C et C++ suivants :

/* Version C */ 
#include <stdlib.h>
void f ( int n ) { int * tableau = calloc ( n , sizeof ( int )); faire_du_travail ( tableau ); libre ( tableau ); }
// Version C++ 
#include <vecteur>
void f ( int n ) { std :: vector < int > array ( n ); faire_du_travail ( array ); }

La version C, telle qu'implémentée dans l'exemple, nécessite une désallocation explicite ; le tableau est alloué dynamiquement (à partir du tas dans la plupart des implémentations C) et continue d'exister jusqu'à ce qu'il soit explicitement libéré.

La version C++ ne nécessite aucune désallocation explicite ; elle se produit toujours automatiquement dès que l'objet arraysort de la portée, y compris si une exception est levée. Cela évite une partie de la surcharge des schémas de récupération de place . Et comme les destructeurs d'objets peuvent libérer des ressources autres que la mémoire, RAII permet d'éviter la fuite des ressources d'entrée et de sortie accessibles via un handle , que la récupération de place par marquage et balayage ne gère pas correctement. Il s'agit notamment des fichiers ouverts, des fenêtres ouvertes, des notifications utilisateur, des objets d'une bibliothèque de dessin graphique, des primitives de synchronisation de threads telles que les sections critiques, des connexions réseau et des connexions au registre Windows ou à une autre base de données.

Cependant, utiliser correctement RAII n'est pas toujours facile et comporte ses propres pièges. Par exemple, si l'on n'est pas prudent, il est possible de créer des pointeurs (ou références) suspendus en renvoyant des données par référence, uniquement pour que ces données soient supprimées lorsque l'objet qui les contient sort de la portée.

D utilise une combinaison de RAII et de récupération de place, en utilisant la destruction automatique lorsqu'il est clair qu'un objet n'est pas accessible en dehors de sa portée d'origine, et la récupération de place dans le cas contraire.

Comptage de références et références cycliques

Les schémas de récupération de place les plus modernes sont souvent basés sur une notion d'accessibilité : si vous n'avez pas de référence utilisable vers la mémoire en question, elle peut être récupérée. D'autres schémas de récupération de place peuvent être basés sur le comptage de références , où un objet est responsable du suivi du nombre de références qui pointent vers lui. Si le nombre descend à zéro, l'objet est censé se libérer et permettre à sa mémoire d'être récupérée. Le défaut de ce modèle est qu'il ne gère pas les références cycliques, et c'est pourquoi de nos jours la plupart des programmeurs sont prêts à accepter le fardeau des systèmes de type mark and sweep, plus coûteux .

Le code Visual Basic suivant illustre la fuite de mémoire liée au comptage de références canonique :

Dim A , B Set A = CreateObject ( "Some.Thing" ) Set B = CreateObject ( "Some.Thing" ) ' À ce stade, les deux objets ont chacun une référence,
Ensemble A . membre = B Ensemble B . membre = A ' Maintenant, ils ont chacun deux références.
Ensemble A = Rien ' Tu pourrais encore t'en sortir...
Ensemble B = Rien ' Et maintenant, vous avez une fuite de mémoire !
Fin

En pratique, cet exemple trivial serait immédiatement repéré et corrigé. Dans la plupart des exemples réels, le cycle de références s'étend sur plus de deux objets et est plus difficile à détecter.

Un exemple bien connu de ce type de fuite est devenu célèbre avec l'essor des techniques de programmation AJAX dans les navigateurs Web dans le problème de l'écouteur inactif . Le code JavaScript qui associait un élément DOM à un gestionnaire d'événements et ne parvenait pas à supprimer la référence avant de quitter, fuyait la mémoire (les pages Web AJAX gardent un DOM donné en vie beaucoup plus longtemps que les pages Web traditionnelles, donc cette fuite était beaucoup plus apparente).

Effets

Si un programme présente une fuite de mémoire et que son utilisation augmente régulièrement, il n'y aura généralement pas de symptôme immédiat. Chaque système physique dispose d'une quantité limitée de mémoire et si la fuite de mémoire n'est pas contenue (par exemple, en redémarrant le programme concerné), elle finira par causer des problèmes.

La plupart des systèmes d'exploitation de bureau modernes disposent à la fois d' une mémoire principale , physiquement hébergée dans des micropuces RAM, et d'un stockage secondaire tel qu'un disque dur . L'allocation de mémoire est dynamique : chaque processus obtient autant de mémoire qu'il en demande. Les pages actives sont transférées dans la mémoire principale pour un accès rapide ; les pages inactives sont déplacées vers le stockage secondaire pour faire de la place, selon les besoins. Lorsqu'un seul processus commence à consommer une grande quantité de mémoire, il occupe généralement de plus en plus de mémoire principale, poussant d'autres programmes vers le stockage secondaire, ce qui ralentit généralement considérablement les performances du système. Même si le programme en fuite est arrêté, il peut falloir un certain temps pour que les autres programmes reviennent dans la mémoire principale et que les performances reviennent à la normale.

Lorsque toute la mémoire d'un système est épuisée (qu'il s'agisse de mémoire virtuelle ou uniquement de mémoire principale, comme sur un système embarqué), toute tentative d'allocation de mémoire supplémentaire échoue. Cela entraîne généralement l'arrêt du programme qui tente d'allouer la mémoire ou la génération d'une erreur de segmentation . Certains programmes sont conçus pour se remettre de cette situation (éventuellement en recourant à la mémoire pré-réservée). Le premier programme à rencontrer le manque de mémoire peut ou non être le programme qui a la fuite de mémoire.

Certains systèmes d'exploitation multitâches disposent de mécanismes spéciaux pour gérer une situation de manque de mémoire, comme l'arrêt aléatoire de processus (ce qui peut affecter des processus « innocents ») ou l'arrêt du plus gros processus en mémoire (qui est vraisemblablement celui qui est à l'origine du problème). Certains systèmes d'exploitation ont une limite de mémoire par processus, pour empêcher qu'un programme ne monopolise toute la mémoire du système. L'inconvénient de cette disposition est que le système d'exploitation doit parfois être reconfiguré pour permettre le bon fonctionnement de programmes qui nécessitent légitimement de grandes quantités de mémoire, comme ceux qui traitent des graphiques, de la vidéo ou des calculs scientifiques.

Le modèle « en dents de scie » d’utilisation de la mémoire : la baisse soudaine de la mémoire utilisée est un symptôme potentiel d’une fuite de mémoire.

Si la fuite de mémoire se situe au niveau du noyau , le système d'exploitation lui-même risque de tomber en panne. Les ordinateurs sans gestion de mémoire sophistiquée, comme les systèmes embarqués, peuvent également tomber en panne à cause d'une fuite de mémoire persistante.

Les systèmes accessibles au public, tels que les serveurs Web ou les routeurs, sont sujets aux attaques par déni de service si un attaquant découvre une séquence d'opérations susceptible de déclencher une fuite. Une telle séquence est appelée exploit .

Un modèle d'utilisation de la mémoire en dents de scie peut être un indicateur d'une fuite de mémoire au sein d'une application, en particulier si les chutes verticales coïncident avec des redémarrages ou des redémarrages de cette application. Il faut cependant faire attention car les points de collecte des déchets peuvent également provoquer un tel modèle et indiquer une utilisation saine du tas.

Autres consommateurs de mémoire

Notez que l'augmentation constante de l'utilisation de la mémoire n'est pas nécessairement la preuve d'une fuite de mémoire. Certaines applications stockent des quantités toujours croissantes d'informations en mémoire (par exemple sous forme de cache ). Si le cache peut devenir si volumineux qu'il pose des problèmes, il peut s'agir d'une erreur de programmation ou de conception, mais il ne s'agit pas d'une fuite de mémoire car les informations restent nominalement utilisées. Dans d'autres cas, les programmes peuvent nécessiter une quantité de mémoire déraisonnablement importante parce que le programmeur a supposé que la mémoire était toujours suffisante pour une tâche particulière ; par exemple, un processeur de fichiers graphiques peut commencer par lire l'intégralité du contenu d'un fichier image et le stocker en mémoire, ce qui n'est pas viable lorsqu'une image très volumineuse dépasse la mémoire disponible.

En d'autres termes, une fuite de mémoire résulte d'un type particulier d'erreur de programmation et, sans accès au code du programme, une personne qui voit des symptômes ne peut que deviner qu'il pourrait s'agir d'une fuite de mémoire. Il serait préférable d'utiliser des termes tels que « utilisation de la mémoire en constante augmentation » lorsqu'il n'existe aucune connaissance interne de ce type.

Un exemple simple en C++

Le programme C++ suivant perd délibérément de la mémoire en perdant le pointeur vers la mémoire allouée.

int main () { int * a = new int ( 5 ); a = nullptr ; /* Le pointeur dans le 'a' n'existe plus et ne peut donc pas être libéré,  mais la mémoire est toujours allouée par le système.  Si le programme continue à créer de tels pointeurs sans les libérer,  il consommera de la mémoire en continu.  Par conséquent, une fuite se produirait. */ }

Plus d articles de Worldlex Wiki

Revenez a l index pour explorer davantage de pages sur l histoire, la science, la culture, la geographie et la societe en francais.

Explorer l index