Grundlegendes zur Speicherverwaltung in modernen Programmiersprachen

Hallo Habr! Ich präsentiere Ihnen die Übersetzung des Artikels " Entmystifizierung der Speicherverwaltung in modernen Programmiersprachen " von Deepu K Sasidharan.

In dieser Artikelserie möchte ich den Schleier der Mystik über die Speicherverwaltung in Software (im Folgenden als Software bezeichnet) auflösen und die Möglichkeiten moderner Programmiersprachen im Detail betrachten. Ich hoffe, dass meine Artikel dem Leser helfen, unter die Haube dieser Sprachen zu schauen und etwas Neues für sich selbst zu lernen.

Eine eingehende Untersuchung der Konzepte der Speicherverwaltung ermöglicht es Ihnen, effizientere Software zu schreiben, da der Stil und die Praxis der Codierung einen großen Einfluss auf die Prinzipien der Speicherzuweisung für die Anforderungen des Programms haben.

Teil 1: Einführung in die Speicherverwaltung


Die Speicherverwaltung besteht aus einer Reihe von Mechanismen, mit denen Sie den Programmzugriff auf den Arbeitsspeicher des Computers steuern können. Dieses Thema ist in der Softwareentwicklung sehr wichtig und verursacht gleichzeitig Schwierigkeiten oder bleibt für viele Programmierer eine Black Box.

Wofür wird RAM verwendet?


Wenn ein Programm auf dem Betriebssystem eines Computers ausgeführt wird, benötigt es Zugriff auf den Arbeitsspeicher (RAM), um:

  • Laden Sie Ihren eigenen Bytecode hoch, um ihn auszuführen.
  • Speichern Sie die Werte von Variablen und Datenstrukturen, die im Prozess verwendet werden.
  • Laden Sie externe Module, die das Programm zum Ausführen von Aufgaben benötigt.

Zusätzlich zu dem Speicherplatz, der zum Laden des eigenen Bytecodes verwendet wird, verwendet das Programm beim Arbeiten zwei RAM-Bereiche - den Stapel (Stapel) und den Heap (Heap).

Stapel


Der Stapel wird zur statischen Zuweisung von Speicher verwendet. Es ist nach dem Prinzip "last come - first come out" ( LIFO ) organisiert. Sie können sich den Stapel als einen Stapel Bücher vorstellen - er darf nur mit dem obersten Buch interagieren: Lesen Sie es oder legen Sie ein neues darauf.

  • Dank des genannten Prinzips können Sie mit dem Stapel schnell Operationen mit Daten ausführen - alle Manipulationen werden mit dem „Top Book in the Stack“ durchgeführt. Das Buch wird ganz oben hinzugefügt, wenn Sie Daten speichern müssen, oder ganz oben, wenn die Daten gelesen werden müssen.
  • , , , — ;
  • — . , , — , . , , , . , , , — , ;
  • ;
  • ; ;
  • ;
  • (stack overflow), . , ;
  • , ;



JavaScript. , .


Der Heap wird verwendet, um Speicher dynamisch zuzuweisen. Im Gegensatz zum Stapel müssen die Daten im Heap jedoch zuerst über das "Inhaltsverzeichnis" gefunden werden. Man kann sich vorstellen, dass ein Heap eine so große mehrstufige Bibliothek ist, in der Sie nach bestimmten Anweisungen das erforderliche Buch finden.

  • Operationen auf dem Heap werden etwas langsamer ausgeführt als auf dem Stapel, da sie einen zusätzlichen Schritt zum Suchen nach Daten erfordern;
  • Der Heap speichert Daten dynamischer Größe, z. B. eine Liste, in die Sie eine beliebige Anzahl von Elementen einfügen können.
  • Heap, der allen Anwendungsthreads gemeinsam ist;
  • Aufgrund seiner Dynamik ist der Heap im Management nicht trivial und mit ihm treten die meisten Probleme und Fehler im Zusammenhang mit dem Speicher auf. Möglichkeiten zur Lösung dieser Probleme bieten Programmiersprachen.
  • , — ( , ), , , ;
  • (out of memory), , ;
  • , , , .


?


Im Gegensatz zu Festplatten ist der Arbeitsspeicher sehr begrenzt (obwohl Festplatten natürlich auch nicht unbegrenzt sind). Wenn ein Programm Speicher verbraucht, ohne ihn freizugeben, absorbiert es letztendlich alle verfügbaren Reserven und versucht, die Speichergrenzen zu überschreiten. Dann wird es einfach von alleine fallen oder, noch dramatischer, das Betriebssystem herunterfahren. Daher ist es höchst unerwünscht, bei Speichermanipulationen in der Softwareentwicklung leichtfertig vorzugehen.

Unterschiedliche Ansätze


Moderne Programmiersprachen versuchen, die Arbeit mit dem Speicher so weit wie möglich zu vereinfachen und den Entwicklern einen Teil der Kopfschmerzen zu nehmen. Obwohl einige ehrwürdige Sprachen immer noch eine manuelle Steuerung erfordern, bieten die meisten immer noch elegantere automatische Ansätze. Manchmal verwendet eine Sprache mehrere Ansätze zur gleichzeitigen Verwaltung des Speichers, und manchmal kann ein Entwickler sogar auswählen, welche Option speziell für seine Aufgaben effektiver ist (ein gutes Beispiel ist C ++ ). Kommen wir zu einem kurzen Überblick über die verschiedenen Ansätze.

Manuelle Speicherverwaltung


Die Sprache bietet keine Mechanismen für die automatische Speicherverwaltung. Die Zuweisung und Freigabe von Speicher für erstellte Objekte liegt ganz im Gewissen des Entwicklers. Ein Beispiel für eine solche Sprache - die C . Es bietet eine Reihe von Methoden ( malloc , realloc , calloc und free ) zum Verwalten des Speichers - der Entwickler muss sie verwenden, um Speicher in seinem Programm zuzuweisen und freizugeben . Dieser Ansatz erfordert große Genauigkeit und Sorgfalt. Es ist auch besonders schwierig für Anfänger.

Müllsammler


Die Speicherbereinigung ist der Prozess der automatischen Speicherverwaltung auf dem Heap, bei dem nicht verwendete Speicherbereiche gefunden werden, die zuvor für die Anforderungen des Programms belegt waren. Dies ist eine der beliebtesten Optionen für die Speicherverwaltung in modernen Programmiersprachen. Die Speicherbereinigungsroutine startet normalerweise in vorgegebenen Zeitintervallen und es kommt vor, dass ihr Start mit ressourcenintensiven Prozessen zusammenfällt, was zu einer Verzögerung in der Anwendung führt. JVM ( Java / Scala / Groovy / Kotlin ), JavaScript , Python , C # , Golang , OCaml undRuby sind Beispiele für beliebte Sprachen, die den Garbage Collector verwenden.

  • Mark & ​​Sweep Garbage Collector : Dies ist ein Algorithmus, der in zwei Phasen ausgeführt wird: Erstens markiert er Objekte im Speicher, auf die verwiesen wird, und gibt dann Speicher von Objekten frei, die nicht markiert wurden. Dieser Ansatz wird beispielsweise in JVM, C #, Ruby, JavaScript und Golang verwendet. In der JVM stehen verschiedene Garbage Collection-Algorithmen zur Auswahl, und JavaScript-Engines wie V8 verwenden zusätzlich zur Linkzählung einen Tagging-Algorithmus. Ein solcher Garbage Collector kann in C und C ++ als externe Bibliothek verbunden werden.

    Visualisierung des Tagging-Algorithmus: Durch Links verknüpfte Objekte werden markiert und nicht erreichbare Objekte gelöscht
  • : — , . . , , , , PHP, Perl Python. C++;


(RAII)


RAII ist eine Software-Sprache in OOP, deren Bedeutung darin besteht, dass der für ein Objekt zugewiesene Speicherbereich streng an seine Lebensdauer gebunden ist. Der Speicher wird im Konstruktor zugewiesen und im Destruktor freigegeben. Dieser Ansatz wurde zuerst in C ++ implementiert und wird auch in Ada und Rust verwendet .

Automatische Linkzählung (ARC)


Dieser Ansatz ist der Speicherbereinigung mit Referenzzählung sehr ähnlich. Anstatt den Zählvorgang jedoch in bestimmten Zeitintervallen zu starten, werden Anweisungen zum Zuweisen und Freigeben von Speicher in der Kompilierungsphase direkt in den Bytecode eingefügt. Wenn der Referenzzähler Null erreicht, wird der Speicher als Teil des normalen Programmflusses freigegeben.

Die automatische Referenzzählung ermöglicht immer noch keine Verarbeitung von Zirkelverknüpfungen und erfordert, dass der Entwickler spezielle Schlüsselwörter für die zusätzliche Verarbeitung solcher Situationen verwendet. ARC ist eine der Funktionen des Clang- Übersetzers und daher in den Sprachen Objective-C und Swift verfügbar . Für die Verwendung in Rust ist auch eine automatische Referenzzählung verfügbar .und die neuen C ++ - Standards mit intelligenten Zeigern .

Besitz


Dies ist eine Kombination von RAII mit dem Konzept des Eigentums, wenn jeder Wert im Speicher nur eine Besitzervariable haben sollte. Wenn der Eigentümer den Ausführungsbereich verlässt, wird der Speicher sofort freigegeben. Wir können sagen, dass dies ungefähr dem Zählen von Links in der Kompilierungsphase entspricht. Dieser Ansatz wird in Rust verwendet und gleichzeitig konnte ich keine andere Sprache finden, die einen ähnlichen Mechanismus verwenden würde.




Dieser Artikel untersuchte die Grundkonzepte im Bereich der Speicherverwaltung. Jede Programmiersprache verwendet ihre eigenen Implementierungen dieser Ansätze und Algorithmen, die für verschiedene Aufgaben optimiert sind. In den folgenden Abschnitten werden wir uns die Speicherverwaltungslösungen in gängigen Sprachen genauer ansehen.

Lesen Sie auch die anderen Teile der Serie:



Verweise





Wenn Ihnen der Artikel gefallen hat, geben Sie bitte ein Plus an oder schreiben Sie einen Kommentar.

Sie können den Autor des Artikels auf Twitter und LinkedIn abonnieren .

Abbildungen: Stapelvisualisierung
mit Pythonontutor .
Abbildung des Eigentümerkonzepts: Link Clark, The Rust-Team unter Creative Commons Namensnennung-Weitergabe unter gleichen Bedingungen Lizenz v3.0 .

Besonderer Dank geht an Alexander Maksimovsky und Katerina Shibakova für den Übersetzungsnachweis

All Articles