Comprendre la gestion de la mémoire dans les langages de programmation modernes

Bonjour, Habr! Je vous présente la traduction de l'article " Démystifier la gestion de la mémoire dans les langages de programmation modernes " par Deepu K Sasidharan.

Dans cette série d'articles, je voudrais dissiper le voile du mysticisme sur la gestion de la mémoire dans les logiciels (ci-après dénommés logiciels) et examiner en détail les possibilités offertes par les langages de programmation modernes. J'espère que mes articles aideront le lecteur à regarder sous le capot de ces langues et à apprendre quelque chose de nouveau pour eux-mêmes.

Une étude approfondie des concepts de gestion de la mémoire vous permet d'écrire des logiciels plus efficaces, car le style et la pratique du codage ont une grande influence sur les principes d'allocation de mémoire pour les besoins du programme.

Partie 1: Introduction à la gestion de la mémoire


La gestion de la mémoire est un ensemble de mécanismes qui vous permettent de contrôler l'accès des programmes à la RAM de l'ordinateur. Ce sujet est très important dans le développement de logiciels et, en même temps, pose des problèmes ou reste même une boîte noire pour de nombreux programmeurs.

Ă€ quoi sert la RAM?


Lorsqu'un programme s'exécute sur le système d'exploitation d'un ordinateur, il a besoin d'accéder à la mémoire vive (RAM) pour:

  • tĂ©lĂ©chargez votre propre bytecode Ă  exĂ©cuter;
  • stocker les valeurs des variables et des structures de donnĂ©es utilisĂ©es dans le processus;
  • charger des modules externes dont le programme a besoin pour effectuer des tâches.

En plus de l'espace utilisé pour charger son propre bytecode, le programme utilise deux zones de RAM lors du travail - la pile (pile) et le tas (tas).

Empiler


La pile est utilisée pour l'allocation statique de mémoire. Il est organisé sur le principe du "dernier arrivé - premier sorti" ( LIFO ). Vous pouvez imaginer la pile comme une pile de livres - elle est autorisée à interagir uniquement avec le livre le plus haut: lisez-le ou mettez-en un nouveau dessus.

  • grâce au principe mentionnĂ©, la pile vous permet d'effectuer rapidement des opĂ©rations avec des donnĂ©es - toutes les manipulations sont effectuĂ©es avec le «livre supĂ©rieur de la pile». Le livre est ajoutĂ© tout en haut si vous avez besoin d'enregistrer des donnĂ©es, ou pris par le haut si les donnĂ©es doivent ĂŞtre lues;
  • , , , — ;
  • — . , , — , . , , , . , , , — , ;
  • ;
  • ; ;
  • ;
  • (stack overflow), . , ;
  • , ;



JavaScript. , .


Le tas est utilisé pour allouer dynamiquement de la mémoire, cependant, contrairement à la pile, les données du tas doivent d'abord être trouvées en utilisant la "table des matières". On peut imaginer qu'un tas est une si grande bibliothèque à plusieurs niveaux, dans laquelle, en suivant certaines instructions, vous pouvez trouver le livre nécessaire.

  • les opĂ©rations sur le tas sont effectuĂ©es un peu plus lentement que sur la pile, car elles nĂ©cessitent une Ă©tape supplĂ©mentaire pour rechercher des donnĂ©es;
  • le tas stocke des donnĂ©es de tailles dynamiques, par exemple, une liste dans laquelle vous pouvez ajouter un nombre arbitraire d'Ă©lĂ©ments;
  • tas commun Ă  tous les threads d'application;
  • en raison de sa nature dynamique, le tas est non trivial dans la gestion et avec lui la majoritĂ© de tous les problèmes et erreurs associĂ©s Ă  la mĂ©moire surviennent. Les moyens de rĂ©soudre ces problèmes sont fournis par les langages de programmation;
  • , — ( , ), , , ;
  • (out of memory), , ;
  • , , , .


?


Contrairement aux disques durs, la RAM est très limitée (bien que les disques durs, bien sûr, ne soient pas non plus illimités). Si un programme consomme de la mémoire sans la libérer, il finira par absorber toutes les réserves disponibles et essayer d'aller au-delà des limites de la mémoire. Ensuite, il tombera tout seul ou, encore plus dramatique, fera tomber le système d'exploitation. Par conséquent, il est hautement indésirable d'être frivole avec des manipulations de mémoire dans le développement de logiciels.

Différentes approches


Les langages de programmation modernes essaient de simplifier le travail avec la mémoire autant que possible et de supprimer une partie du mal de tête des développeurs. Bien que certaines langues vénérables nécessitent toujours un contrôle manuel, la plupart offrent toujours des approches automatiques plus élégantes. Parfois, un langage utilise plusieurs approches pour gérer la mémoire à la fois, et parfois un développeur peut même choisir quelle option sera plus efficace spécifiquement pour ses tâches (un bon exemple est C ++ ). Passons à un bref aperçu des différentes approches.

Gestion manuelle de la mémoire


Le langage ne fournit pas de mécanismes de gestion automatique de la mémoire. L'allocation et la libération de mémoire pour les objets créés restent entièrement dans la conscience du développeur. Un exemple de langage un tel - C . Il fournit un certain nombre de méthodes ( malloc , realloc , calloc et free ) pour gérer la mémoire - le développeur doit les utiliser pour allouer et libérer de la mémoire dans son programme. Cette approche nécessite une grande précision et un grand soin. C'est également particulièrement difficile pour les débutants.

Éboueur


Le garbage collection est le processus de gestion automatique de la mémoire sur le tas, qui consiste à rechercher les portions de mémoire inutilisées qui étaient précédemment occupées pour les besoins du programme. C'est l'une des options les plus populaires pour la gestion de la mémoire dans les langages de programmation modernes. La routine de collecte des ordures démarre généralement à des intervalles de temps prédéterminés et il arrive que son lancement coïncide avec des processus consommant des ressources, ce qui entraîne un retard dans l'application. JVM ( Java / Scala / Groovy / Kotlin ), JavaScript , Python , C # , Golang , OCaml etRuby sont des exemples de langages populaires qui utilisent le garbage collector.

  • Collecteur d'ordures Mark & ​​Sweep: Il s'agit d'un algorithme qui fonctionne en deux phases: tout d'abord, il marque les objets en mĂ©moire qui sont rĂ©fĂ©rencĂ©s, puis libère la mĂ©moire des objets qui n'ont pas Ă©tĂ© reçus. Cette approche est utilisĂ©e, par exemple, dans JVM, C #, Ruby, JavaScript et Golang. Il existe plusieurs algorithmes de rĂ©cupĂ©ration de place diffĂ©rents dans la JVM, et les moteurs JavaScript tels que V8 utilisent un algorithme de balisage en plus du comptage de liens. Un tel garbage collector peut ĂŞtre connectĂ© en C et C ++ en tant que bibliothèque externe.

    Visualisation de l'algorithme de balisage: les objets liés par des liens sont marqués, puis ceux inaccessibles sont supprimés
  • : — , . . , , , , PHP, Perl Python. C++;


(RAII)


RAII est un idiome logiciel en POO, ce qui signifie que la zone de mémoire allouée à un objet est strictement liée à sa durée de vie. La mémoire est allouée dans le constructeur et libérée dans le destructeur. Cette approche a d'abord été implémentée en C ++ et est également utilisée dans Ada et Rust .

Comptage automatique de liens (ARC)


Cette approche est très similaire à la récupération de place avec comptage de références, cependant, au lieu de démarrer le processus de comptage à certains intervalles de temps, les instructions d'allocation et de libération de mémoire sont insérées directement dans le bytecode au stade de la compilation. Lorsque le compteur de référence atteint zéro, la mémoire est libérée dans le cadre du flux de programme normal.

Le comptage automatique des références ne permet toujours pas le traitement des liens circulaires et oblige le développeur à utiliser des mots clés spéciaux pour un traitement supplémentaire de ces situations. ARC est l'une des fonctionnalités du traducteur Clang , il est donc présent dans les langages Objective-C et Swift . De plus, le comptage automatique des références est disponible pour une utilisation dans Rust.et les nouvelles normes C ++ avec des pointeurs intelligents .

Possession


Il s'agit d'une combinaison de RAII avec le concept de propriété, lorsque chaque valeur en mémoire ne doit avoir qu'une seule variable propriétaire. Lorsque le propriétaire quitte la zone d'exécution, la mémoire est immédiatement libérée. On peut dire que c'est à peu près comme compter les liens au stade de la compilation. Cette approche est utilisée dans Rust et en même temps, je n'ai trouvé aucun autre langage qui utiliserait un mécanisme similaire.




Cet article a examiné les concepts de base dans le domaine de la gestion de la mémoire. Chaque langage de programmation utilise ses propres implémentations de ces approches et algorithmes optimisés pour diverses tâches. Dans les parties suivantes, nous examinerons de plus près les solutions de gestion de la mémoire dans les langages populaires.

Lisez aussi les autres parties de la série:



Références





Si vous avez aimé l'article, veuillez mettre un plus ou écrire un commentaire.

Vous pouvez vous abonner Ă  l'auteur de l'article sur Twitter et sur LinkedIn .

Illustrations:
Visualisation de pile effectuée à l'aide de pythontutor .
Illustration du concept de propriété: Link Clark, l'équipe Rust sous licence Creative Commons Attribution Share-Alike v3.0 .

Pour la preuve de la traduction, merci Ă  Alexander Maksimovsky et Katerina Shibakova

All Articles