Das Java Memory Architektur (1. Akt)

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.

Java Memory Architecture

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:

sun-hotspot-memory-1

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: <reason> <stacktrace> (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 <size> bytes for <reason>. 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.

  • Facebook
  • Delicious
  • Digg
  • StumbleUpon
  • Reddit
  • Blogger
  • LinkedIn
Mirko Novakovic

33 Antworten auf Das Java Memory Architektur (1. Akt)

  1. Siva sagt:

    Keep up the good work. I appreciate the blog. I have been looking all over the place for a clear description of the Java Memory Model without breaking my head over the Java Spec. I have not been successful in locating even a single book which talks about all aspects of the memory management in full detail. Take a look at some of these questions in my mind:
    1. Where do static values live? (In Perm Gen as per the blog)
    2. Is the Perm Gen space ever GCed? (Not mandatory according to blog)
    3. If Perm Gen is not GCed, what happens to Weak References and Soft References to static values? Do they have any effect? Is it valid to have Weak and Soft References to Static values? (Mirko, can you please help? Not sure of the answer)

  2. Mirko sagt:

    Hi Siva,

    thank you for your feedback. I am happy you ask these questions, as I think that some of the statements in the blog entry may be confusing…

    Static values live in the Heap and not in the Permanent Generation – except numeric constants and String-constants. As the static objects live in the Heap they are treated like “normal” objects and all rules for Weak and Soft references do apply. In most cases I see these kind of references in combination with collections and than the reference to the static collection class (e.g. a cache) is a normal reference, but the entries are references with a weak or soft reference, e.g. by using a WeakHashMap.

    The JVM specification does not mandate a garbage collection mechanism for the method area. The permanent generation of the Sun HotSpot JVM does have a Garbage Collector. If you turn on -XX:+PrintGCDetails you can see the details of the permanent generation before and after a Full GC. Only classes that are unloaded can be collected by the GC – you can see loading and unloading of classes by turning on these parameters: -XX:+TraceClassloading and -XX:+TraceClassUnloading.

    I hope this helps. more details will follow and the second part of this series will be published this week.

  3. Sebastian sagt:

    Hi Mirko,

    danke für diesen coolen Artikel.
    Er hat mir geholfen das Memory Modell besser verstehen zu können. Ich freu mich schon auf die nächsten Teile :)

    Viele Grüße,
    Sebastian

  4. stephan steiner sagt:

    Hallo Mirko

    ich bin am Zusammenstellen einer Präsentation.
    Dürfte ich Deine tollen Grafiken verwenden?

    Besten Dank
    Gruss Stephan

  5. Hallo Stephan,

    bitte einfach eine kleine “Quelle” Information drunter packen und dann kannst Du die Bilder gerne in Deiner Präsentation verwenden. Die Bilder hat übrigens Torsten aus unserem Marketing Team auf Basis meiner “Skizzen” gemacht.

    Grüße
    Mirko

  6. Pingback: Kari’s World » Blog Archive » Thanks for the memory

  7. Hi Mirko,

    nice post.

    PS: The Java Memory Model has nothing to do with garbage collections but deals with how memory behaves when multi-threading is used; so under which guarantees writes made by one thread are going to be visible in another.

    http://www.cs.umd.edu/~pugh/java/memoryModel/

  8. Hi Peter,

    I know that The Java Memory Model is more frequently used in context of the language specification http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.4 and is more relevant for concurrency issues.

    I wanted to focus on the JVM Specification part to get the java.lang.OutOfMemoryError part a bit clearer.

    Maybe I should change the name of the Blog entry, what do you think would be the best name for the JVM Specification part of the memory model?

    Maybe “Java Memory Architecture”?

    Mirko

  9. Hi Mirko,

    perhaps something with “gc configuration” could be something usable.

  10. Salih sagt:

    Is it possible to know in which space an instance in located? In other words lets say we have an instance of String Object “name”. Which jvm space “name” is located during execution? Is it possible to monitor its movement from surivivor space to eden space?

    Thanks.

  11. Mirko Novakovic sagt:

    Hi Salih,

    there is no way to get the information in which memory are an object is located during runtime from your application.

    There are some JMX information about the areas, e.g. MemoryPoolMXBean, but they do not provide object specific information.

    Maybe you could get more information via a native JVMTI agent (http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html).

    • Andy Song sagt:

      Hi Mirko,

      Actually if we use sun jdk, we have the ability to get object address via “sun.misc.Unsafe”

      static long toAddress(Object obj) {
      Object[] array = new Object[] {obj};
      long baseOffset = getUnsafe().arrayBaseOffset(Object[].class);
      return normalize(getUnsafe().getInt(array, baseOffset));
      }

      static Object fromAddress(long address) {
      Object[] array = new Object[] {null};
      long baseOffset = getUnsafe().arrayBaseOffset(Object[].class);
      getUnsafe().putLong(array, baseOffset, address);
      return array[0];
      }

      Please enjoy that.

  12. Macluq sagt:

    Hi, just pointing out a typo: Maschine in German. Machine in English. ;)

    Thanks for the stuff, it’s pretty cool.

  13. Vamshi sagt:

    Very informative…thanks

  14. Srinivasan sagt:

    Excellent diagram which shows the Java Memory architecture. This kind of article i have been searching for a long time. I shall have to go through it line by line.

  15. Congratulations for the excellent article!!! It really clarifies the main aspects of the Java memory architecture and management.

    I guess the link “optimizations of the Garbage Collection algorithm” should be poiting to http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html instead.

  16. Günther sagt:

    Hallo Mirko,

    verstehe ich es in deiner Grafik richtig, dass jeder Java Thread außerhalb des Heaps/Perm Heaps einen Teil vom RAM belegt? z.B. hatten wir in einer 32bit JVM (auf 64 bit SPARC) die PermSize auf 512 MB, MaxHeap auf 3,2 GB und ziemlich viele Threads offen. Die JVM wurde innerhalb von Minuten instabil und die HeapUsage lag bei 100 %.
    Wenn jeder Thread einen Stack außerhalb des Heaps bekommt, wieviel RAM wird diesem Thread zugewiesen?

    Beste Grüße
    Günther

    • Mirko Novakovic sagt:

      Hallo Günther,

      das siehst Du richtig – jederr Stack belegt einen Anteil im Memory ausserhalb des Heaps. Den Wert kannst Du über -Xss konfigurieren bzw. über eine Limitierung der Thread Stack Size auf OS Ebene. Die Größe des belegten Speichers je Thread hängt vom OS ab.

      Dein beschriebenes Problem muss aber nicht unbedingt mit den Threads zusammen hängen, da ja der Heap voll gelaufen ist. Am Besten Dump ziehen und reinschauen. Fabian hat ja in Teil 4 beschrieben, wie man das am Besten macht und zeigt in Teil 5 bald noch elegantere Wege mit entsprechenden Tools.

      Grüße
      Mirko

      • Günther sagt:

        Hallo nochmal,

        danke für deine ausführliche Antwort. Da wir aber auch “java.lang.OutOfMemoryError: unable to create new native thread” Errors hatten, besteht trotzdem die Vermutung, dass insgesamt nicht genug RAM zur Verfügung stand. Habe aber bereits einen Dump gezogen und werde ihn analysieren.

        Beste Grüße
        Günther

        • Fabian Lange sagt:

          Hallo Günther,
          klingt interessant! Mich interessiert auf jeden Fall die Ursache. Bisher ist mir sowas nur bei fehlerhafter Threadbenutzung begegnet. Hat sich immer mit einer ExecuterService/ThreadPool Lösung lösen lassen. Der Code wurde meist sauberer und zur Verwunderung der Entickler trotz Threadbegrenzung nichtmal langsamer. Tja eine JVM kann nun auch keine Prozessorkerne durch new Thread() herzaubern. Stichwort Starvation.

          Viele Grüße
          Fabian

          • Günther sagt:

            Hallo Fabian,
            fehlerhafte Threadbenutzung ist ein sehr guter Ansatz.
            Der Code wurde seit 2004 weiterentwickelt, unzählige
            Programmierer waren beteiligt und jeder hat seine Ansichten dazu beigetragen. Wir sind schon dabei, den Code
            zu analysieren. Dieser ExecuterService-Ansatz klingt
            interessant, dazu muss mal gegoogled werden ;)

            Ein weiterer Ansatzpunkt ist insbesondere unter Solaris auch die ThreadStackSize – wenn unter einer 32bit-JVM zuviel Heap gegeben wird, bleibt für die Stacks der Threads anscheinend zu wenig RAM übrig. Solaris hat dazu angeblich eine Standardgröße von 1 oder 2 MByte / Stack (je nachdem, ob 32 oder 64bit Solaris). Dh. da könnte uns einiges an Speicher abhanden kommen und wir experimentieren schon mit -Xss, um die ThreadStackSize auf 128k zu begrenzen.

            VG
            Günther

  17. Mirko,

    thank you very much for this interesting post. It filled the gap between theory (heap, stack, etc) and practice (PermGen space error).

    I did not understand one paragraph:
    “The method area can be part of the heap and will be created at runtime. The size of the method area can be static or dynamic and it does not have to provide a Garbage Collector.”
    What do you mean by “provide a Garbage Collector” ?


    Cheers,
    TomekThe method area can be part of the heap and will be created at runtime. The size of the method area can be static or dynamic and it does not have to provide a Garbage Collector.

  18. Prakash Raju sagt:

    Thank you so much Mirko.

    It’s very help full to people like us.

  19. Prakash sagt:

    really good one.Its helpful.

  20. dori sagt:

    Hi!
    How can help me with some exmaple in JMM.

  21. Binh Thanh Nguyen sagt:

    Thanks. Nice post

  22. Nilesh sagt:

    1- how the perm area memory is freed? Is it always grows?
    2- All the consts goes to perm area. So which one is fatser perm or heap ?

  23. Sushma sagt:

    Very Nice article on JVMMemory model…
    Thank u so much for this post :)

  24. Tapan sagt:

    Good Explanation Mirko.

  25. Parixit Singh sagt:

    Thank You very much for the blog.
    It cleared my concept on the various areas of the java program. I was very confused earlier now i am clear with the topic Thank you once again

    Good Work..

  26. Guido Stoff sagt:

    Hallo,

    ich habe immer wieder folgenden Fehler. Ich arbeitete mit recht großen Grafiken 9000×6000 Pixel und drawe sie mit dem Befehl g2d.drawImage(img, affineTransform, this); auf eine Graphics. Wenn die Grafiken kleiner sind läuft das Programm.
    Hat jemand eine Ahnung welcher Speicherbereich hier betroffen ist und wie man Abhilfe schaffen könnte?

    1 W:\akg\Druck_OK\42882.jpg– nach Spiegelung — Free Memory: 1024315352 bytes
    1 W:\akg\Druck_OK\42882.jpg– vor Draw Image — Free Memory: 1024315352 bytes
    java.lang.OutOfMemoryError
    Exception in thread “AWT-EventQueue-0″ java.lang.InternalError: Problem in WPrin
    terJob_drawDIBImage
    at sun.awt.windows.WPrinterJob.drawDIBImage(Native Method)
    at sun.awt.windows.WPrinterJob.drawDIBImage(Unknown Source)
    at sun.awt.windows.WPathGraphics.drawImageToPlatform(Unknown Source)
    at sun.print.PathGraphics.drawImage(Unknown Source)

Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

Du kannst folgende HTML-Tags benutzen: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>