Introduction
I was trying to solve an issue in our code where the@ViewScope
beans were not being garbage collected. I spoke a number of times with Manfred Riem at Oracle about the weirdness of this issue. The issue simply put was that we were facing a memory leak where the instances of @ViewScope
objects were not being removed from the view map. As a result, the pages were being kept in memory. The view map is limited to 32 views which helped to hide the issue. In most cases, it would not appear to normal users of our application. The issue was suddenly evident when the view contained tens of thousands of objects. 32 x 10k is REALLY BIG! It really never made it to 32, the system would stall and crash at about 6 instances.
The Culprit
We had implemented our own customNavigationHandler
. This was working quite well on JSF 2.0.x, but a couple of things happened. The JSF implementation was changed to handle another view scope issue, and our implementation of the NavigationHandler
was changed from my original code. The new handler did not handle cleaning up the @ViewScope
object view map which is stored in the session. Oh, yeah, the view map in the session was the change to the API too.
The Solution
The solution turned out to be something simple, re-implement the same mechanism in the defaultNavigationHandler
to clear the @ViewScope
objects from the view map in the session.
Interesting Observations
I was trying to come up with a mechanism to clear the view map data from the session, and came up with aSystemEventListener
to test out some ideas. I thought I would share the code for people to see how the map is cleared. This is an approach to the issue, but as I noted, it was actually something missed in our NavigationHandler
. I thought I should post the code for anyone who was looking for ideas on how to manipulate the map, or clear data in it. So without further hesitation. Here is the code.
ViewMapSystemEventListener.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | package com.bluelotussoftware.jsf.listener; import com.sun.faces.application.view.ViewScopeManager; import java.util.Map; import javax.faces.component.UIViewRoot; import javax.faces.context.FacesContext; import javax.faces.event.AbortProcessingException; import javax.faces.event.SystemEvent; import javax.faces.event.SystemEventListener; /** * * @author John Yeary * @version 1.0 */ public class ViewMapSystemEventListener implements SystemEventListener { private String currentId; @Override public void processEvent(SystemEvent event) throws AbortProcessingException { /* * 1. Get the source of the event and its view id. * 2. Check if the currentId is null, if so, set it to the source id. * 3. Check the currentId and the new id. * a. If they are the same, continue. We are on the same page. * b. If they are different, clear the view map, and remove from Session map. */ UIViewRoot uivr = (UIViewRoot) event.getSource(); String id = uivr.getViewId(); FacesContext fctx = FacesContext.getCurrentInstance(); ViewScopeManager vsm = ViewScopeManager.getInstance(fctx); if (currentId == null ) { currentId = id; } if (!currentId.equals(id)) { //Clear map and set currentId to new id. vsm.clear(fctx); String key = (String) uivr.getTransientStateHelper().getTransient(ViewScopeManager.VIEW_MAP_ID); uivr.getViewMap().clear(); Map<String, Object> viewMaps = (Map<String, Object>) fctx.getExternalContext().getSessionMap().get(ViewScopeManager.ACTIVE_VIEW_MAPS); viewMaps.remove(key); currentId = id; } } @Override public boolean isListenerForSource(Object source) { return (source instanceof UIViewRoot); } } |
faces-config.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <? xml version = '1.0' encoding = 'UTF-8' ?> < faces-config version = "2.1" xsi:schemaLocation = "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_1.xsd" > < application > < system-event-listener > < source-class >javax.faces.component.UIViewRoot</ source-class > < system-event-listener-class >com.bluelotussoftware.jsf.listener.ViewMapSystemEventListener</ system-event-listener-class > < system-event-class >javax.faces.event.PostConstructViewMapEvent</ system-event-class > </ system-event-listener > </ application > </ faces-config > |