30

Memory per Session grows

We are experiencing high memory consumption using JSF 2.2 (2.2.12) with Mojarra. After investigating our load tests, it turned out that the size of data in our ViewScoped Beans is quite high (sometimes more than 1MB). Anyway - when navigating from view to view, the session memory size grows and grows. We can't decrease the size of the beans on short-term, so this behavior has quite some impact.

Solution 1 - Changing Context Params (not working)

Now - we played around with the official context parameter from Mojarra which are set to 15 by default:

com.sun.faces.numberOfLogicalViews
com.sun.faces.numberOfViewsInSession

Changing those parameters to a lower value did not have any impact on the memory consumption in our load tests.

Solution 2 - Changing activeViewMapsSize (working)

We were debugging Mojarra and found the following Code in ViewScopeManager:

Integer size = (Integer) sessionMap.get(ACTIVE_VIEW_MAPS_SIZE);
if (size == null) {
    size = 25;
}

The default size for keeping the last visited views seems to be 25. Seeing this, we implemented a Session Listener which sets this value to 1:

public class SetActiveViewMapsSizeSessionListener implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent event) {
        event.getSession().setAttribute(ViewScopeManager.ACTIVE_VIEW_MAPS_SIZE, 1);
    }
}

That obviously worked. The memory stopped growing since only 1 view is kept.

So why 25 Views in Memory ?

So Mojarra keeps a history of 25 Views in Memory in case of not a different value is defined in Session. I can't find any documentation about this. Can someone explain what this is for? Is it for Browser Back? We have caching disabled on our JSF pages. So browser back will always create a new view. This shouldn't be an issue for us.

Is Solution 2 a valid approach? Could someone explain the drawbacks of this approach?

Update 1

After various comments and a deeper debugging, it turned out that:

  • com.sun.faces.numberOfLogicalViews defines the logicalViewMap size, which stores only(!) the state of the ui component tree
  • com.sun.faces.application.view.activeViewMapsSize defines the size of the activeViewMap, which holds the ViewScoped beans

When changing numberOfLogicalViews to 1, mojarra will still keep track of all view scoped beans of the last 25 views. When you configure it the other way around - numberOfLogicalViews to 15 and activeViewMapsSize to 1 - the view cannot be correctly initialized due to missing data I guess. We didn't even get an exception. I would like to understand, why mojarra chose to set the activeViewMapsSize higher than the numberOfLogicalViews and not the same since we want to tune our memory consumption without getting an unpredictable behavior.

Update 2

We created an issue at Mojarra: JAVASERVERFACES-4015.

Ahmad MOUSSA
  • 2,729
  • 19
  • 31
fischermatte
  • 3,327
  • 4
  • 42
  • 52
  • which Mojarra version did you try? Also the latest? – Kukeltje Aug 12 '15 at 10:20
  • And take a look which parameters you try to set in the contedxt and which parameters is used in checking how many active views should be used. A quick check in the ViewScopeManager showed this to me. See the difference? Might be the cause of the error/mistake/... Not 100% sure, since it still might not be read from the context. – Kukeltje Aug 12 '15 at 10:23
  • Thx, it is Mojarra 2.2.12, which is currently latest. And I debugged all the parameters - `numberOfLogicalViews` and `numberOfViewsInSession` are applied through the context and by default 15. When changing them to 2, I see 2. `ACTIVE_VIEW_MAPS_SIZE`is by default not defined, so ViewScopeManager takes 25 for the size of the activeViewMap. – fischermatte Aug 12 '15 at 10:36
  • 1
    I mean what if you set the ACTIVE_VIEW_MAPS_SIZE in the context (using the correct string). Did you try that? – Kukeltje Aug 12 '15 at 10:59
  • 1
    I set it in the session map, it worked (Solution 2). There is no WebContextInitParameter like `com.sun.faces.application.view.activeViewMapsSize` (value behind ACTIVE_VIEW_MAPS_SIZE). What I wonder about is the value 25! Why so many. – fischermatte Aug 12 '15 at 11:16
  • 7
    This is an oversight. An embarrassing one. It should have been `numberOfLogicalViews`. The work around is a good one. Alternative is to switch to OmniFaces `@ViewScoped`, which does respect the `numberOfLogicalViews`. – BalusC Aug 12 '15 at 11:30
  • thx @BalusC .Should we open an issue at Mojarra? – fischermatte Aug 12 '15 at 11:34
  • @BalusC Still - do you know which negativ impact it will have when setting this value in mojarra to 1. Is it the same as setting numberOfLogicalViews to 1 if it would work as expected? – fischermatte Aug 12 '15 at 11:54
  • `numberOfLogicalViews` is in detail explained in http://stackoverflow.com/questions/4105439/com-sun-faces-numberofviewsinsession-vs-com-sun-faces-numberoflogicalviews – BalusC Aug 12 '15 at 11:57
  • @Balusc Hm - `activeViewMapsSize` and `numberOfLogicalViews` do not seem to be the same. `activeViewMapsSize` seems to define the size of the map where the viewscoped beans are stored, where `numberOfLogicalViews` is the size of the map where the component state ist stored. I don't get why the values differ. – fischermatte Aug 12 '15 at 17:24
  • 2
    It should technically be same as `numberOfLogicalViews * numberOfViewsInSession`, but `numberOfViewsInSession` is these days rarely useful. At least, every view state should have a view scoped bean attached and there should be no dangling view scoped bean when the associated view state is removed. – BalusC Aug 13 '15 at 06:57
  • 1
    Coming back to this problem, since OmniFaces 2.2, its `@ViewScoped` will destroy as soon as the page unloads and thus not unnecessarily stick around in session anymore. – BalusC Dec 16 '15 at 21:33
  • @fischermatte How did you manage to mesure your memory session? – Rapster Dec 06 '16 at 18:17
  • 1
    @Rapster at that time we were using psi-probe, which had a quite detailed list of all session entries and its size – fischermatte Dec 08 '16 at 12:04
  • Thank you @fischermatte we found our problem with MAT http://www.eclipse.org/mat/ ;) – Rapster Dec 09 '16 at 09:02
  • @fischermatte Did you find a solution for the issue? Could you please share? – Jemo Jan 08 '19 at 11:00

1 Answers1

2

Why does Mojarra keep the ViewScoped Beans of the last 25 Views in Memory?

Because a web page can also be opened in a new browser tab rather than the current browser tab. There is unfortunately no bulletproof way to determine based on a plain vanilla HTTP GET request whether a view is being opened in an existing browser tab or in a new browser tab. Hence all the associated beans are kept in memory regardless of whether the web page is opened in the same browser tab or not.

Anyway - when navigating from view to view, the session memory size grows and grows.

This makes indeed no sense if you navigate from view to view within the same browser tab. But this does make sense when you open the next view in a new browser tab. This way the view in previous browser tab keeps working fine when you switch back to the previous browser tab and continue interacting with the view over there.

We can't decrease the size of the beans on short-term, so this behavior has quite some impact.

It's technically possible to detect in the client side whether the current page has been unloaded or not and notify the server about this condition. These days, the pagehide event can be used to check whether the current view has been destroyed in the client side, and the navigator.sendBeacon can be used to notify the server about this condition in a reliable way (using the combination of e.g. unload and XMLHttpRequest is less reliable as there is no guarantee whether it will actually hit the server on time).

This all is implemented in the logic behind OmniFaces @ViewScoped since OmniFaces 2.2 (November 2015) and across years crystallized into its current shape since OmniFaces 2.7.3 (November 2019). If you're already using CDI to manage beans, then it should be a matter of swapping out import javax.faces.view.ViewScoped; lines in your source code by import org.omnifaces.cdi.ViewScoped; in order to utilize this. In one project I've worked with, the memory usage has decreased with 70% since the migration of native JSF view scoped beans to OmniFaces view scoped beans.

See also:

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555