7
votes

I'm using Mojarra 2.2.4 on GlassFish 4 with Java 7.

As I understand from BalusC's answer to How and when is a @ViewScoped bean destroyed in JSF?, @ViewScoped beans should be destroyed in three cases:

  1. Post-back with non-null outcome
  2. Session expiration
  3. Maximum number of logical views in session exceeded

My beans are being destroyed in the first two cases, but not when the maximum number of logical views is exceeded. I have verified that the beans do expire when the maximum is exceeded (I get a ViewExpiredException), but they are still not destroyed until the session itself expires.

For memory consumption reasons, I would like to have the beans destroyed in this third case, especially since they are not usable after expiration.

Questions

  • Why are the beans not destroyed when they expire?
  • Is this a bug or expected behavior?
  • What would be a clean work-around to make sure the beans get destroyed?

Minimal Example

Here is my bean:

@javax.inject.Named("sandboxController")
@javax.faces.view.ViewScoped
public class SandboxController implements Serializable {
    private static final Logger log = Logger.getLogger(SandboxController.class.getName());
    @PostConstruct
    public void postConstruct() {
        log.log(Level.INFO, "Constructing SandboxController");
    }
    @PreDestroy
    public void preDestroy() {
        log.log(Level.INFO, "Destroying SandboxController");
    }
    public String getData() {
        return "abcdefg";
    }
}

and my sandbox.xhtml:

<?xml version='1.0' encoding='UTF-8' ?>
<!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:h="http://xmlns.jcp.org/jsf/html">
    <body>
        <h:form>
            <h:outputText value="#{sandboxController.data}"/>
        </h:form>
    </body>
</html>

and part of my web.xml:

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

If I refresh the sandbox.xhtml 50 times, I get 50 copies of INFO: Constructing SandboxController in the log. The beans are not destroyed, regardless of how many times I refresh. VisualVM confirms that the beans are still referenced by the UIViewRoot's ViewMap. In my full-size bean, which maintains a fair bit of state, I quickly get an OutOfMemoryException.

When I manually expire the session, I get 50 copies of INFO: Destroying SandboxController.

If I add a submit button to sandbox.xhtml and load it up in 4 different tabs, then try to submit the first one, I get a ViewExpiredException, as expected, but the bean is still not destroyed.

The behavior is the same if I instead use the javax.faces.bean.ManagedBean and javax.faces.view.ViewScoped annotations. However, the OmniFaces annotation org.omnifaces.cdi.ViewScoped works properly.

To clarify...

My @ViewScoped beans are being destroyed on session expiration, unlike problems described in related questions such as Linked ViewScoped beans lead to memory leaks

I am not asking why each bean is not destroyed immediately on subsequent refresh as questioned here: JSF 2.1 ViewScopedBean @PreDestroy method is not called. I want to know why it is that even when they expire, and are no longer useful, they are still not destroyed, and thus continue consuming memory.

1

1 Answers

1
votes

I was able to find a clean work-around by using the OmniFaces @ViewScoped annotation (org.omnifaces.cdi.ViewScoped) instead of the standard @ViewScoped (javax.faces.view.ViewScoped).

The OmniFaces ViewScoped correctly destroys the beans as soon as they expire.

See here for more details: http://showcase.omnifaces.org/cdi/ViewScoped