Communardo Software GmbH, Kleiststraße 10 a, D-01129 Dresden
0800 1 255 255

Was tun bei OutOfMemory - Teil 1: Grundlagen

OutOfMemory ist eines der am häu­figs­ten auf­tre­ten­den Probleme bei Java-basierten Webapplikationen. Die Ursachen dafür sind genau so viel­schich­tig wie die Möglichkeiten der Analyse. Ziel die­ses Artikels ist es, sowohl die Grundlagen wie auch mög­li­che Lösungswege zu zei­gen.

Bevor man jetzt leicht­fer­tig die Software umpro­gram­miert (da man ja genau weiß, wo das Problem liegt), sollte man mit einer aus­führ­li­chen Analyse begin­nen. Nicht sel­ten kommt es vor, dass Software (Parameter der JVM) oder sogar das Betriebssystem nicht kor­rekt kon­fi­gu­riert sind.

Die Process Size

Die Process Size ist der maxi­mal ver­füg­bare Speicher für den Prozess. Dieser ist abhän­gig von der Hardware und vom Betriebssystem. In einer 32 Bit-Architektur kann die Process Size maxi­mal 4 GB sein. Für einen Java-Prozess gibt es fol­gende Bereiche, die im Speicher des Prozesses lie­gen (siehe Abbildung 1):

  • Java Heap - Der Heap ist der Speicher, in dem die Java Objekte erzeugt und ver­wal­tet wer­den.
  • Permanent Generation – Die Permanent Generation ist der Speicher, in dem die JVM sich sel­ber ver­wal­tet. In die­sem Speicher wer­den auch die Klassen durch den ClassLoader gela­den.
  • Nativ Code – Jeder Java-Prozess benö­tigt auch Speicher für den Native Code. Das sind C‑Bibliotheken des Betriebssystems für den Zugriff auf Systemressourcen. Zum Beispiel der Zugriff aufs Dateisystem.

Abbildung 1: Schematischer Aufbau der Speicherverteilung eines Java-Prozesses

Der Speicher für den Native Code ist nicht expli­zit ein­stell­bar. Er ergibt sich aus der Differenz zwi­schen Process Size, Heap und Permanent Generation. Dieser Speicher darf nicht zu knapp bemes­sen sein, denn es kann auch OutOfMemory im Native Code geben.

Die Java Heap Size

Die Java Heap Size gibt die Größe des Speichers an, der für die Erzeugung und Verwaltung der Java-Objekte ver­wen­det wer­den kann. Die Größe des Heaps ist begrenzt. Wird keine Größe expli­zit ange­ge­ben, wird die Defaulteinstellung ver­wen­det. Diese vari­iert von Betriebssystem zu Betriebssystem. Wenn man wirk­lich sicher gehen möchte, stellt man die Größe expli­zit ein:

    -Xms[Bytes]m : initiale Größe des Heaps beim Starten des Prozesses
    -Xmx[Bytes]m : maxi­male Größe des Heaps über den gesam­ten Lebenszeitraum

Der Heap teilt sich in zwei große Bereiche: der New Generation und der Old Generation (siehe Abbildung 2).
Wie die Namen schon ver­mu­ten las­sen, befin­den sich in der New Generation die gan­zen neuen, jun­gen und kurz­le­bi­gen Java-Objekte und in der Old Generation die lang­le­bi­gen Java-Objekte.

Abbildung 2: Aufteilung der Speicherbereiche im Heap

Das Verhältnis zwi­schen New und Old Generation ist aus Speichersicht der größte Unterschied zwi­schen einer Desktop- und einer Serveranwendung. Eine Desktopanwendung läuft in der Regel einen Arbeitstag, also ca. 8 h. Eine Serveranwendung läuft 24 h, 7 Tage die Woche und das meh­rere Monate. Daran kann man schon erken­nen, dass es bei einer Desktopanwendung mehr jün­gere Objekte und bei einer Serveranwendung mehr ältere Objekte gibt. Mit dem Servermodus stellt man das Verhältnis zwi­schen New und Old Generation auf 1:3 ein.

    -server :Servermodus für die VM

Permanent Generation

Die Permanent Generation wird von der JVM für das Laden der Klassen durch den ClassLoader ver­wen­det. Normalerweise ist die Defaulteinstellung der Größe aus­rei­chend. Wenn aller­dings viele Klassen dyna­misch durch die Applikation gela­den wer­den (z.B. durch Reflection), kann die Defaulteinstellung zu Problemen füh­ren. Auch die Verwendung von Persistenz- oder Caching-Frameworks führt zu grö­ße­rem Speicherverbrauch in der Permanent Generation.
Über fol­gende Parameter kann die Defaultgröße der Permanent Generation ange­passt wer­den:

    -XX:PermSize=[Bytes]m : initiale Größe der Permanent Generation
    -XX:MaxPermSize=[Bytes]m : maxi­male Größe der Permanent Generation

Garbage Collection

Man unter­schei­det bei der Garbage Collection in die („ein­fa­che“) Garbage Collection und in die Full Garbage Collection. Bei der Garbage Collection wer­den nicht mehr refe­ren­zierte Objekte aus der New Generation frei­ge­ge­ben. Des Weiteren wer­den noch „lebende“ Objekte, die durch meh­rere Garbage Collection nicht frei­ge­ge­ben wur­den, in die Old Generation kopiert. Bei einer Full Garbage Collection wer­den auch Objekte aus der Old Generation auf­ge­räumt.
Eine Full Garbage Collection ist sehr zeit­in­ten­siv (bei 2 GB Heap Size = meh­rere Sekunden). Während die­ser Zeit reagiert die Applikation nicht mehr.
Ist die Heap Size zu gering ein­ge­stellt, fin­det eine Full Garbage Collection nach der ande­ren statt. Dies führt zu einer sehr schlech­ten Performance und zu lan­gen Antwortszeiten.
In einer Multiprozessor-Maschine ist es des­halb rat­sam, die Garbage Collection („ein­fa­che“ und Full) par­al­lel arbei­ten zu las­sen.

    -XX:+UseParallelGC: par­al­lele Garbage Collection und Full Garbage Collection

Ausblick

Nachdem in die­sem Artikel die Grundlagen erläu­tert wur­den, wer­den im nächs­ten Artikel kon­krete Analyse-Möglichkeiten bespro­chen.

Links

Related Posts

Pin It on Pinterest