Currently we are developing a new JEE application that amongst others makes use of CDI and JSF. The application runs on a Jetty server where Weld 2.2.1.Final was added as CDI-implementation and Mojarra 2.2.6 as JSF-implementation. A while ago we noticed that sometimes the server halted without any obvious reason. It did not crash but simply was not able to perform any further user-action. The only solution was to restart the server.
A look into the server logs showed an java.lang.OutOfMemoryError: GC overhead limit exceeded
. This error means that the garbage collector uses a big amount of the cpu time. Monitoring the server using jvisualvm confirmed this statement: after the server was in use for a while it had a cpu load of 100% and therefore it was not able to process any further actions. Additionally the heap size increased continuously but did not reach the maximum heap size. So what was the reason for this error? VisualGC another monitoring tool showed the reason for the OutOfMemoryError
. The JVM divides the heap space into different sections. One for the short-lived (young) objects and one for the long lived objects. If an object can not be garbage collected for a while it will be moved to the section with the long living objects. VisualGC is able to display these different memory sections and showed that the memory section with the long living objects was filled to 100%. This causes the garbage collector to try to release memory all the time but no objects could be removed.
Analyzing the heap dump pointed out that there were several beans in memory that should no more have been there because their scope was already destroyed. The commonality of the beans was that they all had been annotated with @ViewScoped
. We found that there was one reference left to each of these beans that prevented them from getting garbage collected. The references were hold in a map in the FacesContext accessible by
FacesContext.getCurrentInstance().getExternalContext.getSessionMap().
get("com.sun.faces.application.view.activeViewContexts")
This map contains further maps that contained the references. The beans had another commonality: they all have been injected in beans annotated with @FlowScoped
. So obviously the references to the beans got not properly deleted when the view scope or at least the flow scope was destroyed.
The solution for this was to implement an observer that listens to a “view-scope-destroyed-event” and then manually clear the map that holds the references to our no longer needed beans:
@SessionScoped
public class ViewDestroyedListener implements Serializable {
private static final long serialVersionUID = 7420601040662802473L;
public void observeViewDestroy(@Observes @Destroyed(ViewScoped.class) {
UIViewRoot root) {
ExternalContext ctx = FacesContext.getCurrentInstance().
getExternalContext();
Object mapObject = ctx.getSessionMap().
get("com.sun.faces.application.view.activeViewContexts");
// Traverse value-maps and clear them
}
}
We suppose that there somewhere is a bug in JSF or Weld so we created a bug report for this issue. It can be found here