1. Überblick über den Speicher eines Java‑Prozesses
Wenn Sie ein Java‑Programm starten, fordert die JVM (Java Virtual Machine) beim Betriebssystem ein Stück Speicher an. Manchmal – bescheiden, manchmal – ziemlich ansehnlich (vor allem, wenn Sie zum Beispiel irgendein Minecraft mit vielen Mods starten). Dieser Speicher wird in mehrere Schlüsselbereiche aufgeteilt, von denen jeder seine Rolle spielt:
- Stack (Stack) – für lokale Variablen und Methodenaufrufe.
- Heap (Heap) – für alle Objekte, die Sie mit new erzeugen.
- Laufzeitinterne Bereiche (PermGen/MetaSpace) – für Klassen‑Metadaten, statische Felder und andere „magische“ Dinge.
So sieht es ungefähr aus:
┌───────────────────────────────┐
│ JVM-Prozess │
│ ┌─────────────┐ │
│ │ Stack │ ← Jeder Thread – sein eigener Stack!
│ └─────────────┘ │
│ ┌─────────────┐ │
│ │ Heap │ ← Gemeinsam für alle Threads
│ └─────────────┘ │
│ ┌───────────────┐ │
│ │ PermGen/ │ ← Klassenmetadaten
│ │ MetaSpace │
│ └───────────────┘ │
└───────────────────────────────┘
Warum ist das wichtig?
- Das Verständnis des Speicheraufbaus hilft, effizienteren und sichereren Code zu schreiben.
- Fehler wie StackOverflowError oder OutOfMemoryError lassen sich einfacher diagnostizieren.
- Begriffe wie „Garbage Collector“ und „Speicherleck“ schrecken nicht – Sie wissen, wo und was zu suchen ist.
2. Stack (Stapel): schnell, lokal, aber nicht für immer
Der Stack ist ein spezieller Speicherbereich, der für jeden Thread separat zugewiesen wird. Der Stack ähnelt einem Stapel Teller: Den zuletzt abgelegten nehmen Sie als Ersten erst, nachdem Sie alle anderen abgenommen haben. Der Stack arbeitet also nach dem LIFO‑Prinzip (Last In, First Out).
Wozu dient der Stack?
Im Stack werden gespeichert:
- Lokale Variablen von Methoden (zum Beispiel int x = 5; innerhalb einer Methode).
- Rücksprungadresse nach einem Methodenaufruf (damit bekannt ist, wohin nach dem Ende der Methode zurückgesprungen wird).
Jedes Mal, wenn Sie eine Methode aufrufen, wird dem Stack ein neuer Frame (stack frame) hinzugefügt – so etwas wie eine Box, in der alle lokalen Variablen dieser Methode und Verwaltungsinformationen liegen. Wenn die Methode beendet ist, wird ihr Frame entfernt – alle lokalen Variablen verschwinden.
Beispiel
public static void main(String[] args) {
int a = 10; // a liegt im Stack von main
int b = sum(a, 5); // sum aufrufen
}
public static int sum(int x, int y) {
int result = x + y; // x, y, result liegen im Stack von sum
return result;
}
- Wenn sum aufgerufen wird, wird dafür ein eigener Frame im Stack erzeugt.
- Nach dem Ende von sum verschwinden seine Variablen.
Lebenszyklus einer Variable
Lokale Variablen leben nur so lange, wie die Methode ausgeführt wird, in der sie deklariert sind. Sobald die Methode beendet ist – sind sie weg, der Speicher wird sofort freigegeben.
Stacküberlauf
Falls Sie versehentlich (oder absichtlich) eine endlose Rekursion geschrieben haben, fügt jeder Methodenaufruf einen neuen Frame zum Stack hinzu. Irgendwann ist der Stack voll, und Sie erhalten:
Exception in thread "main" java.lang.StackOverflowError
Beispiel:
public static void main(String[] args) {
recurse();
}
public static void recurse() {
recurse(); // Endlose Rekursion!
}
Stack‑Größe
Die Stack‑Größe ist begrenzt – üblicherweise sind es einige Megabyte pro Thread (per Parameter -Xss einstellbar). Ist der Stack voll – stürzt das Programm mit einem Fehler ab.
3. Heap: der Platz für Ihre Objekte
Der Heap ist der gemeinsame Speicherbereich für alle Threads, in dem alle Objekte leben, die Sie mit new erstellen, sowie Arrays. Genau im Heap passiert die ganze Magie der objektorientierten Programmierung.
Wie gelangen Objekte in den Heap?
String s = new String("Hello");
int[] arr = new int[10];
- Die Variable s ist eine Referenz und liegt im Stack.
- Das String-Objekt und das Array arr liegen im Heap.
Lebenszyklus eines Objekts
Ein Objekt lebt im Heap, solange es mindestens eine starke Referenz (strong reference) auf dieses Objekt gibt. Sobald niemand mehr auf das Objekt verweist – wird es zu „Müll“ und kann vom Garbage Collector (GC) entfernt werden.
Speicherverwaltung
Im Gegensatz zu C/C++, wo Sie selbst für die Freigabe von Speicher sorgen müssen (free, delete), übernimmt das in Java der GC. Sie können ein Objekt nicht explizit freigeben, aber alle Referenzen auf null setzen – dann wird es ein Kandidat für die Entsorgung.
Schema: Wo liegt was?
Stack (main)
└─ s ─┬────────────┐
│ │
▼ │
Heap │
┌─────────────┐ │
│ String "Hello"◄──┘
└─────────────┘
Besonderheiten des Heaps
- Es gibt genau einen Heap pro JVM‑Prozess.
- Die Größe des Heaps kann beim Start festgelegt werden (-Xmx, -Xms).
- Wenn im Heap kein freier Speicher mehr bleibt und der GC nicht freigeben kann – stürzt das Programm mit OutOfMemoryError ab.
4. PermGen und MetaSpace: Wo „leben“ Klassen?
Wenn Sie class MyClass { ... } schreiben und das Programm dann starten, muss die JVM alles, was zu dieser Klasse gehört, irgendwo speichern – Methoden, Felder, Bytecode, statische Variablen, Konstanten und sogar String‑Literale. Dafür gibt es in der JVM einen speziellen Speicherbereich, in dem Klassen „leben“.
Früher, bis Java 8, hieß dieser Bereich PermGen (Permanent Generation). Er hatte jedoch einige Probleme – zum Beispiel eine feste Größe; wenn der Platz nicht reichte, stürzte die Anwendung einfach mit OutOfMemoryError: PermGen space ab.
Mit Java 8 kam ein neuer, flexiblerer Bereich – MetaSpace. Er ersetzte den alten PermGen und kann nun automatisch wachsen und so viel Speicher belegen, wie das System benötigt (im Rahmen des verfügbaren physischen Speichers).
PermGen (bis Java 8)
- Im PermGen wurden Klassen‑Metadaten, statische Felder und String‑Literale gespeichert.
- Die Größe des PermGen war begrenzt (standardmäßig klein), man konnte sie mit -XX:MaxPermSize=256m erhöhen.
- Wenn eine Anwendung viele Klassen dynamisch nachlud (z. B. in Web‑Servern), konnte der PermGen „voll laufen“, und Sie erhielten den Fehler:
java.lang.OutOfMemoryError: PermGen space
- Problem: Das Aufräumen des PermGen funktionierte nicht immer korrekt, wenn Klassen dynamisch entladen wurden (z. B. beim Neustart von Web‑Anwendungen).
MetaSpace (ab Java 8)
- Seit Java 8 ist PermGen verschwunden, und es gibt den MetaSpace.
- MetaSpace speichert Klassen‑Metadaten nun im nativen Speicher (außerhalb des Java‑Heaps).
- Die Größe des MetaSpace ist standardmäßig nicht begrenzt (nur durch den Systemspeicher), kann aber per -XX:MaxMetaspaceSize=512m limitiert werden.
- Der Fehler bei Speichermangel sieht nun so aus:
java.lang.OutOfMemoryError: Metaspace
- In den MetaSpace gelangen ebenfalls statische Felder, Methoden und Klasseninformationen.
Schema: Wie alles aufgebaut ist
┌───────────────────────────────┐
│ JVM-Prozess │
│ ┌─────────────┐ │
│ │ Stack │ ← Lokale Variablen, Methodenaufrufe
│ └─────────────┘ │
│ ┌─────────────┐ │
│ │ Heap │ ← Objekte, Arrays, alles, was über new kommt
│ └─────────────┘ │
│ ┌───────────────┐ │
│ │ MetaSpace │ ← Klassenmetadaten, statische Felder
│ └───────────────┘ │
└───────────────────────────────┘
Warum ist das wichtig?
Wenn Sie gewöhnliche Desktop‑ oder Server‑Anwendungen schreiben, stoßen Sie wahrscheinlich nie auf PermGen‑ oder MetaSpace‑Fehler. Wenn Sie jedoch mit dynamischem Klassenladen arbeiten (z. B. Plug‑ins, Web‑Anwendungen, Frameworks wie Spring, die viele Klassen laden und entladen können), ist Wissen über den MetaSpace ein Must‑have!
5. Abbildung: JVM‑Speicherschema
flowchart TD
subgraph JVM
direction TB
Stack1["Stack (Thread 1)"]
Stack2["Stack (Thread 2)"]
Heap[Heap]
MetaSpace[MetaSpace]
end
Stack1 --verweist auf--> Heap
Stack2 --verweist auf--> Heap
Heap --verwendet Klassen aus--> MetaSpace
- Jeder Thread hat seinen eigenen Stack.
- Alle Stacks können auf Objekte im Heap verweisen.
- Objekte im Heap „kennen“ ihre Klasse, deren Informationen im MetaSpace liegen.
6. Beispiel: So sieht es im echten Code aus
public class MemoryDemo {
public static void main(String[] args) {
int x = 42; // x liegt im Stack von main
String s = "Hello!"; // s - Referenz im Stack, String-Objekt im Heap, Literal "Hello!" im MetaSpace
Person p = new Person("Alice"); // p - Referenz im Stack, Person-Objekt im Heap
// Wir rufen eine Methode auf, um einen neuen Stack-Frame zu erzeugen
printPerson(p);
}
public static void printPerson(Person person) {
// person - Referenz im Stack von printPerson
System.out.println(person.getName());
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() { return name; }
}
Erläuterung:
- x – lokale Variable, lebt im Stack der Methode main.
- s – Referenz im Stack, das String-Objekt im Heap, und das String‑Literal "Hello!" – im MetaSpace.
- p – Referenz im Stack, das Objekt Person im Heap.
- Die Klasse Person und alle ihre Methoden/Felder – im MetaSpace (Klassen‑Metadaten).
- Der Aufruf printPerson(p) erzeugt einen neuen Stack‑Frame; darin zeigt die lokale Referenz person auf dasselbe Objekt im Heap.
7. Wie die JVM Speicher verwaltet: kurzes FAQ
Kann ich den Stack steuern?
Nein, der Stack steht vollständig unter Kontrolle der JVM. Sie können nur seine Größe beim Start festlegen (-Xss).
Kann ich den Heap steuern?
Teilweise: Die Heap‑Größe wird beim Start festgelegt (-Xmx, -Xms). Für die Bereinigung ist der Garbage Collector (GC) zuständig.
Kann ich den MetaSpace steuern?
Sie können die Größe begrenzen (-XX:MaxMetaspaceSize), in der Regel ist das aber nicht nötig.
Was passiert bei Speichermangel?
– Ist der Stack voll – StackOverflowError.
– Ist der Heap voll – OutOfMemoryError: Java heap space.
– Ist der MetaSpace voll – OutOfMemoryError: Metaspace.
8. Typische Fehler beim Umgang mit Speicher
Fehler Nr. 1: StackOverflowError wegen endloser Rekursion. Der häufigste Grund – man hat die Abbruchbedingung der Rekursion vergessen. Zum Beispiel ruft sich eine Methode ununterbrochen selbst auf. Die JVM kann den Stack nicht unendlich erweitern, und das Programm „stürzt ab“.
Fehler Nr. 2: OutOfMemoryError wegen vollem Heap. Wenn Sie zu viele Objekte erzeugen, auf die weiterhin Variablen/Kollektionen verweisen (z. B. fügen Sie Elemente zu einer Liste hinzu, löschen sie aber nie), kann der Heap voll werden.
Fehler Nr. 3: OutOfMemoryError: PermGen space / Metaspace. Wenn Sie Plug‑ins verwenden oder viele Klassen dynamisch laden und der MetaSpace nicht bereinigt wird (z. B. aufgrund falschen Entladens von Klassen), kann der Platz im MetaSpace ausgehen.
Fehler Nr. 4: Verwechslung zwischen Referenz und Objekt. Viele Einsteiger verwechseln: Eine Variable vom Typ Person im Stack ist nur eine Referenz, das Objekt selbst liegt im Heap.
Fehler Nr. 5: Erwartung, dass der Garbage Collector sofort alles löscht. Der GC arbeitet „nach Laune“ (in Wahrheit – nach internen Algorithmen und bei Speichermangel) und nicht sofort, nachdem Sie eine Referenz auf null gesetzt haben. Man sollte nicht mit sofortiger Speicherfreigabe rechnen.
GO TO FULL VERSION