When developing a site in JSF many people like to use the Richfaces Framework. It is a pretty solid framework that comes wit a lot of functionality and nice components. It also comes with an Ajax framework called Ajax4JSF (also called a4j). When deploying this site then facing the Internet to production, many people start finding out that their applications eat a lot of memory, leading to unresponsive systems or OutOfMemoryError crashes. This is due to a design issue in JSF / A4J and cannot be easily fixed, but worked around. But lets start with an analysis of whats wrong with our otherwise nice application.
Finding the culprit
To find out whats wrong the best is to create a heap dump at peak usage to show whats consuming all the memory.
After opening that dump in the wonderful Eclipse MAT you might get a picture like this:
Ouch. 1.8 GB out of 2GB are consumed by sessions. I usually then filter by “StandardSession” for apache sessions to be able to easily browse around the sessions that have high amount of retained heap.
Ouch again… 10MB per session, this cannot scale. Now it could of course be possible that some careless programmer put in much of our data into the session, but thats simply not true, as we can easily find out:
Thats interesting. Almost all of our memory is consumed by AjaxStateHolders. So what does this actually do?
How JSF and A4J work
I try to keep it simple. Before JSF actually renders HTML to the users browser, it builds an internal representation. For each page (or view) this internal representation (called component tree) is created and run through the lifefcycle. Any user events are processed. If there is a component where a user can select one of 3 items, those 3 items are loaded and a string representation put onto the page. The component keeps track of which item is selected.
Now we do not want the user to submit the whole page to select another item, which would cause the component tree to be recreated and a different item to be selected. We want Ajax! For that A4J remembers the state of the components when the page was displayed. So it knows the component tree and those 3 items, and which one was selected. On AJAX actions the component is looked up, the state modified by selecting another item and the partial HTML representation sent back. This is also called partial page rendering.
So how does the remembering work? Well you might have guessed it: It creates a AjaxStateHolder in the user session and attaches the component tree to it.
How this can get big
Ok, this might be the current page. I can have big views but it is only one. No thats unfortunately not true. A4J does store more than one view. But why? Look at your browser, you are quite likely to find a “back” button somewhat in the to-left corner. When you click that you see the last page. But the browser did not send a request, so the server does not know that you are on that page. Imagine you click AJAX functionality: Where should the server get the component tree from for that time you have been on the page? Easy answer: This is as well in the AjaxStateHolder. By default this tracks back 16 views. and because you might come across the same view in your browsing history more than once there are up to 16 variants of a view.
As you can see this can get big. And remember, this is per user.
How to fix
Unfortunately there is no fix. This is how JSF works, and how A4J works.
There is a ticket on the Richfaces bug tracker which describes this: RF-3878 – Session memory leak. And the best answer is: reduce the number of views you store.
<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>
You will loose the browser back button functionality, but gain a lot of memory. Second way to fix is to reduce the size of the component tree, which is most likely affected by big lists of data and complex structures. However this is easier said than done.