Vergessener Speicher im Überfluss
In der Vorlesung für Betriebssysteme habe ich zwar die ‚Speicherstruktur‘ gelernt. Begriffe wie Stack und Heap kamen auch in Prüfungen vor.
Aber ehrlich gesagt habe ich mir bis zu meinem Abschluss kaum ernsthafte Gedanken über den Speicher gemacht. Heutzutage haben Laptops standardmäßig 16GB oder 32GB RAM. Bei Aufgaben auf Bachelor-Niveau wird der Speicher praktisch nie knapp. Zudem räumen Sprachen wie Java dank des Garbage Collectors (GC) automatisch auf, sodass ich mich nicht um Speicheradressen kümmern musste.
So stürzte ich mich, berauscht vom Überfluss der Hardware und der Bequemlichkeit der Sprache, ins Berufsleben – ohne das ‚Gespür für Speicher‘, das eigentlich zur Grundausstattung eines Entwicklers gehört.
Eine schmerzhafte Lektion aus der Praxis
Beim Üben für Coding-Tests in der Uni hantierte ich höchstens mit ein paar Zahlen in einem Array. Die Eingabedaten überstiegen selten 100.000 Stück. Aber die Praxis war anders. Selbst in einem kleinen Startup häuften sich in der Datenbank (DB) Millionen von echten Kundendaten.
In der Praxis nutzen wir Technologien wie ‚ORM (Object Relational Mapping)‘, um diese riesigen Datenmengen bequem zu handhaben. Es ist ein dankbares Werkzeug, das es erlaubt, Daten wie Java-Objekte zu behandeln, ohne SQL-Queries schreiben zu müssen. (In der Java-Welt ist Hibernate der Standard.)
Das Problem war, dass dieses Werkzeug ‚zu bequem‘ ist. Ein Knopfdruck genügt, um Daten aus der DB als Liste zu laden. Ich konnte das Gewicht der Daten, das sich dahinter verbarg, nicht einschätzen. Ich schrieb nur eine Zeile Code: „Hol mir die Kundeninfos“. Aber damit schaufelte ich die Daten von zehntausenden Kunden mitsamt ihrer Bestellhistorie komplett in den Speicher.
Das Ergebnis war verheerend. Der 32GB RAM war im Nu voll. Der Server litt unter ‚extremen Lags‘, weil der GC (die Reinigungskraft) verzweifelt versuchte, Speicher freizugeben, und blieb schließlich stehen.
Bekannter Stack, fremder Heap
Als ich die Fehlerprotokolle durchging, die der Server ausgespuckt hatte, blieb mein Blick an einem Wort hängen.
java.lang.OutOfMemoryError: Java heap space
Das Wort ‚Stack‘ war mir ziemlich vertraut. Ich hatte es in Datenstrukturen bis zum Überdruss gelernt, und es ist der Name der Website, die jeder Entwickler täglich besucht (Stack Overflow). Ich wusste auch, dass der Stack platzt, wenn man rekursive Funktionen falsch schreibt.
Aber der Übeltäter, dem ich in der Praxis begegnete, wenn der Server starb, war nie der Stack. Die Logs zeigten immer auf den ‚Heap‘.
„Der Stack ist nicht voll, sondern der Heap-Platz fehlt?“
Ein Zweifel schoss mir durch den Kopf. Den Stack verstand ich, aber was zum Teufel ist der Heap, dass er in der Praxis so oft explodiert? Ist das derselbe Heap wie in den Datenstrukturen? Warum quält mein Code den Heap und nicht den Stack?
Diese Frage führte mich zurück zu den verstaubten Fachbüchern und in die Welt von Google. Und ich erkannte: Mein ‚Arbeitsspeicher (RAM)‘, das Zuhause meines Codes, ist kein einzelnes Einzimmer-Apartment, sondern ein ‚getrennter Raum‘, der je nach Verwendungszweck strikt unterteilt betrieben wird: in Stack und Heap.

Werkbank und Lagerhalle im digitalen Logistikzentrum
Kehren wir zu unserem Weltbild des ‚digitalen Logistikzentrums‘ zurück. Hier ist der ‚RAM (Speicher)‘ der Platz, an dem der Arbeiter (CPU) seine Materialien ausbreitet, um zu arbeiten. Aber dieser Raum wird aus Effizienzgründen strikt in zwei Bereiche geteilt.
1. Stack: Die private Werkbank des Arbeiters
- Merkmal: Ein schmaler, hoher Schreibtisch, der direkt vor dem Arbeiter (CPU) steht.
- Zweck: Hier werden nur die Variablen (lokale Variablen, Parameter) kurz abgelegt, die für die gerade ausgeführte Aufgabe (Funktion) benötigt werden.
- Lebensdauer: Wenn die Aufgabe (Funktion) erledigt ist, wird der Schreibtisch sofort abgeräumt (Pop). Die Verwaltung ist sehr einfach und schnell.
- Metapher: Zutaten auf dem Schneidebrett beim Kochen. Nach dem Kochen wird sofort aufgeräumt.
2. Heap: Die öffentliche Lagerhalle
- Merkmal: Ein riesiges Lagerhaus hinter der Werkbank. Es bietet viel Platz, aber es dauert, Dinge dort zu finden.
- Zweck: Hier werden große Daten oder solche, die lange aufbewahrt werden müssen (Objekte, Instanzen), gelagert.
- Lebensdauer: Sie bleiben so lange dort, bis der Benutzer sie wegwirft oder die Reinigungskraft (Garbage Collector) sie entsorgt.
- Metapher: Der Kühlschrank oder die Speisekammer. Auch nach dem Kochen bleiben die Zutaten dort.

[Code Verification] Zwei Welten, bewiesen durch Fehler
Ist der Speicher wirklich so aufgeteilt? Wir können die Existenz dieser zwei Räume – Stack und Heap – beweisen, indem wir absichtlich Fehler provozieren.
1. Den Stack sprengen (StackOverflowError)
Der Stack ist eine ‚Werkbank‘. Die Werkbank ist eng. Wenn eine Funktion nicht endet und sich ständig selbst aufruft (Rekursion), stapeln sich die Akten auf der Werkbank bis zur Decke und stürzen schließlich ein.
public class StackTest {
public static void recursiveCall(int depth) {
// Unendliche Rekursion: Stapelt sich weiter auf der Werkbank (Stack), ohne zu enden
System.out.println("Stack Depth: " + depth);
recursiveCall(depth + 1);
}
public static void main(String[] args) {
recursiveCall(1);
}
}
Ergebnis: Nach ein paar tausend Durchläufen stürzt es mit einem StackOverflowError ab. Selbst wenn der Heap-Raum völlig leer ist, stirbt das Programm, wenn der Stack (die Werkbank) voll ist.
2. Den Heap sprengen (OutOfMemoryError)
Nun rekonstruieren wir meinen Albtraum. Ähnlich wie bei der falschen Nutzung von ORM, bei der zehntausende Objekte auf einmal geladen wurden, erstellen wir ständig riesige Listen und stopfen sie in das Lagerhaus (Heap).
import java.util.ArrayList;
import java.util.List;
public class HeapTest {
public static void main(String[] args) {
List<byte[]> warehouse = new ArrayList<>();
while (true) {
// Erzeuge kontinuierlich 1MB Daten und staple sie im Lager (Heap)
// Praxis-Beispiel: Passiert, wenn man zehntausende Daten aus der DB ohne Paging abruft
warehouse.add(new byte[1024 * 1024]);
}
}
}
Ergebnis: java.lang.OutOfMemoryError: Java heap space. Das ist kein Problem der Werkbank (Stack). Der Fehler tritt auf, weil im Lagerhaus (Heap) kein Platz mehr ist, um Waren zu stapeln. Das ist der Moment, in dem wir mit eigenen Augen sehen, wie unser Code den Heap quält.
Die Existenz der Reinigungskraft: Garbage Collector (GC)
Hier gibt es einen wichtigen Unterschied. Der Stack leert sich ‚automatisch‘, wenn die Funktion endet. Man muss sich nicht darum kümmern. Aber im Heap sammelt sich Müll, wenn ihn niemand wegräumt.
In alten Sprachen wie C musste der Entwickler mit dem Befehl free() selbst putzen. Vergaß man das, füllte sich das Lager mit Müll und platzte (Memory Leak). Moderne Sprachen wie Java, Python oder JavaScript hingegen haben eine professionelle Reinigungskraft namens ‚GC (Garbage Collector)‘ eingestellt.
„Benutzt niemand mehr dieses Objekt?“ Der GC geht periodisch durch den Heap, findet herrenlose Objekte und wirft sie weg. Dank ihm müssen wir keinen Code zur Speicherfreigabe schreiben.
Aber nichts ist umsonst. In dem Moment, in dem der GC den Großputz macht, stoppen kurzzeitig alle Arbeiten im Logistikzentrum (Stop-the-world). Dass das Spiel plötzlich ruckelt oder der Server für eine Sekunde stockt, liegt genau an dieser Putzzeit.
Fazit: Das Auge für das Unsichtbare
Nachdem ich Stack und Heap verstanden hatte, begann ich, den Code auf meinem Monitor anders zu sehen. Früher dachte ich bei new Student() einfach: „Ich habe ein Objekt erstellt.“ Aber jetzt sehe ich es: „Aha, jetzt ist eine Kiste in das Lagerhaus namens Heap gewandert. Das wird Speicher fressen, bis ich es lösche (oder der GC kommt).“
Seit ich dieses ‚Auge für das Unsichtbare‘ habe, konnte ich mich von der vagen Angst befreien. Auch wenn der Server ein OutOfMemory ausspuckt, drücke ich nicht mehr panisch den Neustart-Knopf wie früher. Stattdessen schalte ich ruhig die Analysetools ein und frage: „Welches Objekt hat den Heap besetzt?“ Denn wenn man die Ursache kennt, kann man sie beheben.
Wir haben nun den Raum (Memory), in dem der Code gespeichert wird, erobert. Das Logistiklager ist perfekt vorbereitet. Wer sind dann aber die ‚Arbeiter‘, die in diesem Lager tatsächlich Kisten schleppen und montieren? Was ist der Unterschied, ob man alleine arbeitet oder mit vielen gleichzeitig (Multi-Tasking)?
Im nächsten Teil reisen wir in die Welt von ‚Prozess und Thread‘, der Blüte des Betriebssystems und der ewigen Hausaufgabe für Backend-Entwickler.