Memory leak with ViewScoped bean?
Asked Answered
H

3

6

In our JavaEE6 project (EJB3, JSF2) on JBoss 7.1.1, it seems we have a memory leak with SeamFaces @ViewScoped.

We made a little prototype to check the fact :

  • we use JMeter to call a page 200 times;
  • the page contains and calls a viewscoped bean which injects a stateful EJB;
  • we fix the session timeout at 1 minute.

At the end of the test, we check the content of the memory with VisualVM, and here what we got:

  • with a @ViewScoped bean, we still get 200 instances of the stateful MyController - and the @PreDestroy method is never called;
  • with a @ConversationScoped bean, @preDestroy method is called a the session end and then we got a clean memory.

Do we badly use the view scope, or is it truly a bug?


Here's the XHTML page:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:f="http://java.sun.com/jsf/core"
   xmlns:h="http://java.sun.com/jsf/html"
   xmlns:ui="http://java.sun.com/jsf/facelets"   
   xmlns:s="http://jboss.org/seam/faces">
   <f:metadata>
        <f:viewParam name="u" value="#{myBean.uselessParam}" />
        <s:viewAction action="#{myBean.callService}" />
   </f:metadata>
   <h:body >
        <f:view>
        </f:view>
   </h:body>    
</html>

Now the included bean myBean. For the @ConversationScoped variant, all commented parts are uncommented.

@ViewScoped
// @ConversationScoped
@Named
public class MyBean implements Serializable 
{
    @Inject
    MyController myController;
    //@Inject
    //Conversation conversation;

    private String uselessParam;

    public void callService()
    {
        //if(conversation.isTransient())
        //{
        //            conversation.begin();
        //}
        myController.call();
    }

    public String getUselessParam() 
    {
        return uselessParam;
    }

    public void setUselessParam(String uselessParam) 
    {
        this.uselessParam = uselessParam;
    }
}

And then the injected stateful bean MyController:

@Stateful
@LocalBean
public class MyController
{
   public void call()
   {
         System.out.println("call ");
   }

   @PreDestroy
   public void destroy()
   {
         System.out.println("Destroy");
   }
}
Harpsichord answered 29/8, 2012 at 16:52 Comment(0)
B
5

I see many developers are satisfied with @ViewAccessScoped in Myface CODI. Could you please give it a try and tell the feedback.

Betoken answered 30/8, 2012 at 3:25 Comment(0)
H
5

I have faced the above mentioned problem in JSF managed @ViewScoped bean. After referring to few blogs I understood that JSF saves view bean states in http session and gets destroyed only when session is invalidated. Whenever we click on the jsf page every time new view scope bean referred in page is created. I did a work around using Spring Custom View Scope. It works fine. Below is the detail code.

For JSF 2.1:

Step 1: Create a View Scope Bean Post Construct Listener as follows.

    public class ViewScopeBeanConstructListener implements ViewMapListener {

     @SuppressWarnings("unchecked")
     @Override
     public void processEvent(SystemEvent event) throws AbortProcessingException {
        if (event instanceof PostConstructViewMapEvent) {
            PostConstructViewMapEvent viewMapEvent = (PostConstructViewMapEvent) event;
            UIViewRoot viewRoot = (UIViewRoot) viewMapEvent.getComponent();
            List<Map<String, Object>> activeViews = (List<Map<String, Object>>) 
                 FacesContext.getCurrentInstance().getExternalContext().getSessionMap(). get("com.org.jsf.activeViewMaps");
            if (activeViews == null) {
                activeViews = new ArrayList<Map<String, Object>>();
                activeViews.add(viewRoot.getViewMap());
                FacesContext.getCurrentInstance().getExternalContext().getSessionMap(). put("com.org.jsf.activeViewMaps", activeViews);
            } else {
                activeViews.add(viewRoot.getViewMap());
            }
        }
    }

Step 2: Register event listener in faces-config.xml

<system-event-listener>
    <system-event-listener-class>
         com.org.framework.custom.scope.ViewScopeBeanConstructListener
    </system-event-listener-class>
   <system-event-class>javax.faces.event.PostConstructViewMapEvent</system-event-class>
    <source-class>javax.faces.component.UIViewRoot</source-class>
</system-event-listener>

Step 3: Create a Custom View Scope bean as follows.

 public class ViewScope implements Scope {

    @Override
    public Object get(String name, ObjectFactory objectFactory) {
        Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
            if (viewMap.containsKey(name)) {
                    return viewMap.get(name);
            } else {
                 List<Map<String, Object>> activeViewMaps = (List<Map<String, Object>>)
                 FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("com.org.jsf.activeViewMaps");
                 if (activeViewMaps != null && !activeViewMaps.isEmpty() 
                     && activeViewMaps.size() > 1) {
                        Iterator iterator = activeViewMaps.iterator();
                     if (iterator.hasNext()) {
                             Map<String, Object> oldViewMap = (Map<String, Object>)
                             iterator.next();
                             oldViewMap.clear();
                             iterator.remove();
                     }
                  }
                Object object = objectFactory.getObject();
                viewMap.put(name, object);
                return object;
           }

    }

Note : Other overridden methods can be empty.

For JSF 2.2:

JSF 2.2 saves the navigated view maps in http session in 'com.Sun.faces.application.view.activeViewMaps' as key. So add the below code in Spring Custom View Scope. No need of listeners as in JSF 2.1

public class ViewScope implements Scope {

    public Object get(String name, ObjectFactory objectFactory) {
         Map<String, Object> viewMap =  
           FacesContext.getCurrentInstance().getViewRoot().getViewMap();
              if (viewMap.containsKey(name)) {
                     return viewMap.get(name);
              } else {
                       LRUMap lruMap = (LRUMap) FacesContext.getCurrentInstance().
     getExternalContext().getSessionMap().get("com.sun.faces.application.view.activeViewMaps");
                if (lruMap != null && !lruMap.isEmpty() && lruMap.size() > 1) {
                   Iterator itr = lruMap.entrySet().iterator();
                   while (itr.hasNext()) {//Not req
                     Entry entry = (Entry) itr.next();
                     Map<String, Object> map = (Map<String, Object>) entry.getValue();
                     map.clear();
                     itr.remove();
                     break;
                   }
                }
                Object object = objectFactory.getObject();
                viewMap.put(name, object);
                return object;
        }
 }
Hellenize answered 3/2, 2014 at 15:26 Comment(3)
can you please share the imports for LRUMap and others in your code? There are many import options to choose from and it is failing currently.Bernal
Hi @Sathish Kumar, I know It's quite older this post. But It'll be really helpful if you guide me with the same issue I am facing. I am not able to override public Object get(String name, ObjectFactory objectFactory). I am getting the method is public TypeVariable<?> lookup(String name). I am using the JSF-API version 2.1.3. Could you please help me out.Massicot
@Sathish Kumar, is there any need to synchronize the calls to "get" method of "ViewScope"?Subcritical
R
4

Chances are this is a bug. Honestly the Seam 3 implementation wasn't all that great and the CODI one (and also what will be in DeltaSpike) is much better.

Ritualism answered 29/8, 2012 at 22:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.