Richfaces Session Speicherverbrauch – Analyse eines Memory-Leaks

1 Kommentar

Für die Entwicklung von JSF basierten Webseiten ist das Richfaces Framework ein gern genommenes Werkzeug. Es ist ein solides Framework mit umfassender Funktionalität, guter Dokumentation und einer Vielzahl von Komponenten. Mitgeliefert wird auch ein Ajax Framework namens Ajax4JSF (auch a4j genannt). Deployt man nun eine solche Anwendung in das weite Internet in Produktion, so stellen viele Leute fest, dass der Speicherverbrauch stark ansteigt und in nicht mehr reagierenden Systemen oder OutOfMemoryError Abstürzen resultiert. Die Ursache hierfür ist ein Designproblem in JSF / A4J und kann leider nicht einfach behoben, jedoch aber umgangen werden. Beginnen wir aber zuerst mit einer Analyse des Problems in einer ansonsten gut funktionierenden Anwendung.

Ursachenforschung

Bei Speicherproblemen ist das Mittel der Wahl fast immer ein HeapDump um festzustellen was genau den Speicher belegt.
Nachdem man nun einen solchen Dump in dem fabelhaften Eclipse MAT geöffnet hat, so erhält man zum Beispiel ein Bild wie dieses:

Ouch. 1.8 GB der 2GB sind von Sessions belegt. Ich filtere dafür üblicherweise nach „StandardSession“ um die Apache Sessions einzeln zu sehen und mich leicht in diesen umsehen zu können. Besonderes Augenmerk lege ich darauf was relativ viel Speicher belegt (retained heap).

Schonwieder ouch… 10MB pro Session. Das kann nicht funktionieren. Nun es wäre möglich, daß ein unvorsichtiger Programmierer so viele Daten in die Session gelegt hat, doch dies stimmt nicht, wie wir leicht herausfinden können:

Interessant! Fast der gesamte Speicher einer Session wird also von AjaxStateHolders belegt. Also, was genau macht dieses Ding eigentlich?

Wie JSF und A4J arbeiten

Ich beschreibe das Verhalten etwas vereinfachend. Bevor JSF das HTML für den Browser des Benutzers erzeugt, baut es eine interne Darstellung auf. Für jede Seite (oder View) wird diese interne Darstellung (auch Component Tree genannt) erstellt und durch den JSF lifecycle geschleust. Jegliche Benutzerinteraktionen werden dabei verarbeitet. Gibt es zum Beispiel eine Auswahlkomponente mit 3 Einträgen, so werden diese 3 Einträge gelesen und an den Component Tree gehängt. Die Komponente selber hält fest welcher Eintrag ausgewählt ist.

Nun wollen wir nicht, dass der Benutzer die Seite abschicken muss, um seine Auswahl zu tätigen, welches in einem Neuaufbau des Component Tree resultieren würde. Wir möchten dafür Ajax verwenden! Damit dies möglich wird, muss sich A4J an den Zustand der Komponenten erinnern wie er war als die Seite erzeugt wurde. Die Komponente und die 3 Einträge sind also bekannt und wenn der Benutzer eine auswählt, wird der Zustand der Komponente verändert und der Teil der Seite aktualisiert auf dem die Komponente angezeigt wird. Dies nennt man teilweise Seitendarstellung (partial page rendering).

Wie funktioniert nun dieses „Erinnern“? Ganz einfach: Es erzeugt einen AjaxStateHolder in der Benutzersession und hängt an diesen den Component Tree.

Es wächst

Ok, es ist also die aktuelle Seite und sein Component Tree. Das können zwar große Bäume sein, aber doch nur einer pro Benutzer? Dies ist leider nicht ganz richtig. A4J speichert mehr als nur einen View. Aber warum? Nun, Browser haben einen Knopf, der es erlaubt auf die vorherige Seite zurückzukehren. Klickt man diesen Knopf an, so wird aber nicht die Seite neu angefordert, sondern aus dem Browsercache dargestellt. Der Server weiß also garnicht, dass der Browser die letzte Seite anzeigt. Benutzt man dann Ajax Funktionalität, so muss der Server den Zustand kennen, der zu dem Zeitpunkt auf dem Server existiert hat. Woher soll der Server diesen Zustand noch kennen? Einfach: Dieser ist natürlich auch im AjaxStateHolder. Standardmäßig werden 16 Views gespeichert. Und weil man während einer Sitzung an dem gleichen View mehrmals vorbeikommen kann, werden jeweils 16 Varianten der Views erstellt.

Offensichtlich kann dies doch sehr viel werden. Und das pro User.

Ein Lösungsansatz

Leider gibt es keine Lösung. So funktioniert JSF und so funktioniert A4J.

Im Richfaces Bug Tracker gibt es auch ein Ticket zu dem Thema: RF-3878 – Session memory leak. Und die beste Antwort ist: Reduziere die Anzahl der gespeicherten Views.

<context-param>
 <description></description>
 <param-name>com.sun.faces.numberOfViewsInSession</param-name>
 <param-value>1</param-value>
</context-param>
<context-param>
 <description></description>
 <param-name>com.sun.faces.numberOfLogicalViews</param-name>
 <param-value>1</param-value>
</context-param>

Man verliert Teile der „Zurück“ Knopf Funktionalität, gewinnt aber Unmengen von Speicher.
Die zweite Lösung ist die Component Trees drastisch zu reduzieren. Diese sind aber wahrscheinlich so groß weil es komplexe Strukturen und große Datenlisten gibt, welche sich sehr schwer reduzieren lassen können.

Fabian Lange ist Lead Agent Engineer bei Instana und bei der codecentric als Performance Geek bekannt. Er baut leidenschaftlich gerne schnelle Software und hilft anderen dabei, das Gleiche zu tun.
Er kennt die Java Virtual Machine bis in die letzte Ecke und beherrscht verschiedenste Tools, die JVM, den JIT oder den GC zu verstehen.
Er ist beliebter Vortragender auf zahlreichen Konferenzen und wurde unter anderem mit dem JavaOne Rockstar Award ausgezeichnet.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentare

Kommentieren

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