Beliebte Suchanfragen

Cloud Native

DevOps

IT-Security

Agile Methoden

Java

|
//

Das Java Memory Architektur (1. Akt)

6.1.2010 | 7 Minuten Lesezeit

Eine der großen Stärken der Java Plattform ist die automatische Speicherverwaltung. Jeder der schon mal mit C/C++ programmiert hat und sich um die Allokation und Freigabe von Speicher selber kümmern musste, weiß diese Eigenschaft der Java Runtime zu schätzen. Die in der Praxis häufig aufgetretenen Probleme, dass Speicher zu früh  (corrupted pointer) oder unvollständig (memory leak ) freigegeben wurden sind damit grundsätzlich behoben. Die Frage ist: Warum schreibe ich dann überhaupt diese Blog Einträge?

Leider kann auch eine implizite Speicherverwaltung, wie Java sie hat, nicht verhindern, dass man Anwendungen programmiert, die zu Speicher Problemen führen, obwohl eine manuelle Allokation in Java nicht möglich und per Spezifikation ausgeschlossen ist. Das Resultat solcher Fehler ist dann in der Regel eine Ausnahme vom Typ: java.lang.OutOfMemoryError .

In diesem ersten Teil der Serie über Java OutOfMemoryError, möchte ich die Grundlagen der Java Memory Architektur erklären und zeigen in welchen Speicherbereichen OutOfMemoryError aufreten können. Über die genauen Ursachen und Möglichkeiten der Analyse wird dann in den nachfolgenden Artikeln berichtet.

Beginnen wir mit der Beschreibung des OutOfMemoryErrors:

Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector.

Die Beschreibung in der aktuellen Java API (Version 6) ist nicht nur sehr kurz, sondern aus meiner Sicht auch unvollständig und damit eigentlich sogar falsch. Die Beschreibung bezieht sich leider nur auf den Heap der Java Virtual Maschine – wir werden später sehen, dass OutOfMemoryError auch in anderen Speicherbereichen der JVM auftreten können, was in der kurzen Beschreibung  leider nicht erwähnt wird.

Die Architektur der Speicherverwaltung wird in der Java Virtual Maschine Specification für alle JVM Implementierungen festegelegt. Kapitel 3.5 Runtime Data Areas und 3.6 Frames sind dabei von entscheidender Bedeutung. Für ein besseres Verständnis habe ich die Informationen der Spezifikation einmal in einem Schaubild grafisch zusammengefasst.

Grundsätzlich kann man zur Laufzeit zwischen Speicherbereichen unterscheiden, die von allen Threads geteilt werden und solchen, die exklusiv nur einem Thread zur Verfügung stehen. Die beiden Bereiche, die allen Threads zur Verfügung stehen sind die Method Area und der Heap.

Die Method Area ist der Speicherbereich der JVM in dem die Repräsentation jeder geladenen Klasse verwaltet wird. Dazu übergibt ein Class-Loader den Bytecode an die JVM, die dann die interne Repräsentation der Klasse in der Methode Area erzeugt. Die interne Repräsentation der Klasse enthält die nachfolgenden Datenbereiche:

  • Runtime Constant Pool
    Numerische Konstanten der Klasse vom Typ int, long, float oder double, String-Konstanten, sowie symbolische Referenzen aller Methoden, Attribute und Typen der Klasse.
  • Methoden Code
    Die Implementierung aller Methoden der Klasse.
  • Attribute
    Eine Liste aller namentlichen Attribute der Klasse.
  • Felder
    Werte aller Felder der Klasse als Referenzen über den Runtime Constant Pool.

Die Method Area kann Teil des Heaps sein und wird beim Start der JVM erzeugt. Die Größe der Method Area kann statisch oder dynamisch sein und es muss keine automatische Speicherverwaltung (sprich: Garbage Collection) für diesen Bereich zur Verfügung gestellt werden.

Der Heap verwaltet die Instanzen von Klassen und Arrays zur Laufzeit und steht allen Threads der JVM zur Verfügung. Der Heap wird beim Start der JVM erzeugt und seine Größe kann statisch oder dynamisch sein. Die JVM Spezifikation sieht für den Heap verpflichtend eine automatische Speicherverwaltung in Form eines Garbage Collectors vor. Die Art und Implementierung des Garbage Collectors wird nicht vorgegeben, allerdings wird eine explizite Deallokation von Objekten auf dem Heap durch den Programmierer ausgeschlossen.

Schauen wir uns die beiden Bereiche mal am Beispiel der Sun HotSpot Implementierung an:

Der Heap ist in der Grafik farbig dargestellt und besteht aus zwei Generationen: Der Young Generation und der Tenured Generation. Die Details zum Aufbau des Generationen Heaps sind aus Sicht von Java OutOfMemoryError Problemen nicht sonderlich relevant, sondern dienen hauptsächlich der Optimierung der Garbage Collection . In der Sun HotSpot Implementierung ist die Method Area in einem eigenen Speicherbereich implementiert: In der Permanent Generation. Details zurKonfiguration und Überwachung der einzelnen Speicherbereiche der JVM werde ich im dritten Teil dieser Blog Serie Konfiguration und Überwachung der JVM beschreiben. Das Beispiel zeigt allerdings schon sehr gut, dass die Java Sepcification zwar die Architektur, also den Rahmen, des Java Memory Modells vorgibt, die einzelnen Implementierungen aber sehr unterschiedlich sein können.

Neben Heap und Method Area, die für alle Threads innerhalb einer JVM zur Verfügung stehen, wird für jeden Java Thread ein eigener Speicherbereich reserviert, auf den dieser dann exklusiven Zugriff hat. Für ein besseres Verständnis sollen die drei Teilbereiche nachfolgend kurz beschrieben werden:

  • PC Register
    PC steht dabei für Program Counter. Wird eine nicht native Methode von einem Thread ausgeführt, dann zeigt dieses Register auf die Java Virtual Maschine Instruktion die gerade von diesem Thread ausgeführt wird. Im Falle einer nativen Methode ist der Inhalt des PC Registern nicht definiert.
  • Java Virtual Maschine Stack
    Jeder Thread erhält einen eigenen Speicherbereich, in dem so genannten Frames für jeden Methoden-Aufruf erzeugt werden, die der Thread ausführt, d.h. bei verschachtelten Methodenaufrufen können mehrere Frames gleichzeitig innerhalb des JVM Stacks existieren, allerdings ist immer nur der Frame der gerade ausgeführten Methode „aktiv“. Jeder Frame enthält die lokalen Variablen der Methode, eine Referenz auf den Runtime Constant Pool der Klasse dieser Methode und einen Operanden Stack in dem die Befehle der JVM ausgeführt werden. (JVM ist ein Kellerautomat !)
  • Native Methode Stack
    Native Methoden erhalten einen eigenen Stack, den so genannten „C-Stack“.

Jetzt wo wir ein grundlegendes Verständnis vom Aufbau des Java Memory Modells haben, können wir uns dem eigentlichen Thema wittmen: java.lang.OutOfMemoryError. Wie Eingangs erwähnt, ist die Javadoc dieser Klasse wenig aussagekräftig, allerdings wird in der Java Virtual Maschine Specification genau spezifiziert, wann dieser Fehler auftritt.  Im Prinzip können die OutOfMemory Ausnahmen in jedem Speicherbereich auftreten – an Hand der Sun HotSpot JVM sollten die konkreten Ausnahmen zugeordnet werden.

Im Heap treten Speicher Fehler auf, wenn der Garbage Collector nicht mehr genug Speicher frei räumen kann, um eine Speicheranfrage für ein neues Objekt zu erfüllen. Ein solcher Fehler im Heap führt bei der Sun HotSpot Implementierung zu nachfolgender Fehlermeldung:

Exception in thread "main": java.lang.OutOfMemoryError: Java heap space

Eine Variation davon ist

Exception in thread "main": java.lang.OutOfMemoryError: Requested array size exceeds VM limit

die beim Versuch geworfen wird ein Array auf dem Heap anzulegen, das größer ist als der Heap selber.

Neben Fehlern im Heap können aber auch in der Method Area OutOfMemoryError auftreten, wenn nicht genug Speicher zur Verfügung steht, um beispielsweise Klassen Informationen abzulegen. In der Sun HotSpot Implementierung führt diese zu einem Fehler im PermGen Space:

Exception in thread "main": java.lang.OutOfMemoryError: PermGen space

Bei Thread exklusivem Speicher treten OutOfMemoryError seltener auf, sind dann aber häufig um so schwieriger zu beheben. Unter anderem sind in der Sun HotSpot Implementierungen folgende Fehler Texte möglich:

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

Exception in thread "main": java.lang.OutOfMemoryError: (Native method)

Der erste Fehler wird geworfen, wenn zu viele Threads innerhalb der JVM bestehen und nicht mehr genug Speicher zur Verfügung steht, um einen neuen nativen Thread anzulegen. Dies kann beispielsweise dadurch kommen, dass das Speicherlimit für den Java Prozess auf Betriebssystem Ebene erreicht ist oder beispielsweise die maximale Anzahl File Handles für den ausführenden Benutzer erreicht wurde. Die zweite Meldung zeigt an, dass ein Speicher Allokationsfehler auf dem Native Stack aufgetreten ist, also in einer Methode, die über JNI aufgerufen wurde.

Wichtig sei noch zu erwähnen, dass kein OutOfMemory Error geworfen wird, wenn auf dem JVM Stack kein Speicher mehr  zur Verfügung steht, um einen neuen Frame anzulegen. Die Ausnahme in dieser Situation ist laut Spec ein java.lang.StackOverflowError .

Die letzte mir bekannte Variante des OutOfMemoryError mit einer Sun HotSpot Implementierung ist

Exception in thread "main": java.lang.OutOfMemoryError: request bytes for . Out of swap space?

Dieser Fehler wird beispielsweise dann geworfen, wenn der Heap erweitert werden muss, aber auf Betriebssystem Seite nicht mehr genügend Speicher zur Verfügung steht – beispielsweise, weil der Swap Space zu klein definiert wurde oder der Speicher durch einen anderen Prozess belegt wird.

Mit diesem Blog Eintrag wollte ich die Grundlagen für das Verständnis von Java OutOfMemoryError erklären. Es ist aus meiner Sicht wichtig zu verstehen, dass es in Java mehr relevante Speicherbereiche als den Heap gibt und ein OutOfMemory Fehler ganz unterschiedliche Ursachen haben kann. Trotzdem sind natürlich viele Fragen offen geblieben. Beispielsweise, welche konkreten Ursachen es gibt (z.B. Java Memory Leaks) und wie man die unterschiedlichen Speicherbereiche überwacht und konfigurieren kann. Genau das sind die beiden nächsten Themen dieser Java OutOfMemoryError Blog Reihe , gefolgt von detaillierten Anleitungen zur Analyse der besprochenen Problemfelder.

|

Beitrag teilen

Gefällt mir

3

//

Weitere Artikel in diesem Themenbereich

Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.