JSF 2.2 Memory Consumption: Why does Mojarra keep the ViewScoped Beans of the last 25 Views in Memory? - jsf

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.

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:
com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews
How detect and remove (during a session) unused #ViewScoped beans that can't be garbage collected
JSF: Mojarra vs. OmniFaces #ViewScoped: #PreDestroy called but bean can't be garbage collected

Related

How to invalidate views for the current session in JSF?

We have an application (Mojarra 2.3) where a user can change a global filter of the data it sees. When that happens, I want to keep the session, but invalidate the active views (which are server side).
I found this question which enables you to count the number of views: How do I count the number of views in a user's JSF session (JSF 2.2)?
Based on that I figured I could remove the attribute in which the views are stored. I came up with this method:
public static void invalidateViews() {
final HttpSession session = Faces.getSession();
List.of("com.sun.faces.application.view.activeViewContexts",
"com.sun.faces.application.view.activeViewMaps",
"com.sun.faces.renderkit.ServerSideStateHelper.LogicalViewMap",
"org.jboss.weld.context.ConversationContext.conversations")
.forEach(session::removeAttribute);
Faces.redirect(Faces.getRequest().getRequestURL().toString());
}
The com.sun.faces.renderkit.ServerSideStateHelper.LogicalViewMap was mentioned in the linked question's answer. I kind of guessed that it would not hurt to remove the other attributes. The answer also mentions that it is Mojarra only.
It seems to work so far, but I would like to ask this: should one clear server side views like this? And, if so, how can I support MyFaces as well?
It seems to work so far, but I would like to ask this: should one clear server side views like this?
Given that there is no standard API call for this, this is about the best you can do, yes. I have during the JSF 2.3 work requested for a more specific variant of this functionality to exist in the standard API because I needed to be able to destroy view scoped beans associated with a specific JSF view state (for the OmniFaces view scope unload functionality, see Hacks#removeViewState()). But this has unfortunately not yet been fleshed out because of difficulties with Portlets.
And, if so, how can I support MyFaces as well?
As seen in the OmniFaces Hacks helper class, the session attribute key for MyFaces 2.x is org.apache.myfaces.application.viewstate.ServerSideStateCacheImpl.SERIALIZED_VIEW and 4.x org.apache.myfaces.application.viewstate.StateCacheServerSide.SERIALIZED_VIEW.
By the way,
Faces.redirect(Faces.getRequest().getRequestURL().toString());
this is shorter:
Faces.refreshWithQueryString();

JSF - Appropriate bean scope for keeping data between pages but only "browser tab related"

I am creating a web application using JSF 2.2.20 in which I am implementing a "kinda wizard" flow which lets the user filling input fields and go back and forth the view pages through navigation. I am using a single bean for all these views.
Let's say I have views A.xhtml, B.xhtml, C.xhtml and D.xhtml, all managed by the same bean MyBean.java
I want my application to be "browser tab scoped", which means that
I do not want my bean's data be re-instantiated after every HTTP Request as it happens with #RequestScoped beans or after view changing as it happens with #ViewScoped, I want the data of my bean to be kept between view changes and redirections so the user can go back and forth between pages without losing the data he has already given.
I do not want to use the #SessionScoped scope since each time the user opens a new tab I want the bean to be re-instantiated starting from page "A.xhtml.
Is there any built-in way to achieve the scenario described above using the current JSF version? In case there is not any, could you please propose any workarounds?
Thanks in advance!
I think #ViewScoped is what you are looking for, but it depends on your exact usage.
Couple of notes:
Use javax.faces.view.ViewScoped. Don't use the deprecated managed bean annotation as it works differently.
#ViewScoped works by storing the beans in the view. So each time you load the page you get a view and a viewId that corresponds to that view. So effectively each load of the page (could be read as 'each browser tab') gets its own bean.
#ViewScoped is a passivating scope. That means your beans, and their injected Dependencies, do need to be Serializable.
Use a recent, up-to-date version of your app server, or if you bring in MyFaces manually, use the latest release. I found a number of older versions implementations buggy 5+ years ago, but it seems to work flawlessly now.
If there is a Page Navigation occurring, you probably want to use FlowScoped. This is a multi-page bean that stays alive until you end the 'flow'.
If neither of these two work, you can always implement your own scope which is surprisingly easy with CDI.

JSF: Mojarra vs. OmniFaces #ViewScoped: #PreDestroy called but bean can't be garbage collected

This question is specific to the OmniFaces #ViewScoped bean (however of interest to wider discussion about memory leakage and resource disposal with JSF #ViewScoped). It is based on the results of this NetBeans8.1 test web app available on GitHub:
Investigation of undesirable holding of references to various forms of JSF #ViewScoped beans by navigation type
https://github.com/webelcomau/JSFviewScopedNav
That test web app has a comprehensive README with complete instructions, as well as annotated test web pages comparing obsolete JSF2.0-style #ManagedBean #ViewScoped, JSF2.2-style CDI-friendly #Named #ViewScoped, and OmniFaces #Named #ViewScoped beans.
The results using JVisualVM for diagnosis are summarised in a downloadable spreadsheet (see also screenshot below), and indicate that while the OmniFaces-2.5.1 #ViewScoped bean invokes #PreDestroy methods under GET-based navigation cases when leaving a view (giving the opportunity to release most resources), it does not seem to permit garbage collection of the actual bean (at least not with the current context parameter settings).
In web.xml the application is set to use:
com.sun.faces.numberOfViewsInSession 4
com.sun.faces.numberOfLogicalViews 4
By default this OmniFaces-specific parameter is commented out:
org.omnifaces.VIEW_SCOPE_MANAGER_MAX_ACTIVE_VIEW_SCOPES
The javax.faces.STATE_SAVING_METHOD defaults to 'server'.
The main question is:
Q1: Is it correct that these OmniFaces #ViewScoped beans can't by design be garbage collected "live" (meaning through say provocation using a Profiler's garbage collectiong action, not waiting until a session is over) ?
Q2: If this is so, how can (should) one best force release of them on navigating away from pages (especially under GET navigations) ?
Q3: If not so (if my results are incorrect because of some other setting) why aren't I witnessing provoked garbage collection of them, and what can I do to ensure they are indeed automatically released ?
Since the test web app is downloadable, well documented and hopefully self-explanatory, I won't give code here, but simply the comparitive results so far, as well as screenshots of the test web app pages in action:
The cause of this problem seems to be due to strange behaviour JVisualVM when attached to Glassfish/Payara.
The test case used for this question is still extremely useful, however the conclusions concerning Garbage Collection in the original post (and images) were based on JVisualVM, and I have since found they are not valid.
Use the NetBeans Profiler instead !
I am now getting completely consistent results for OmniFaces ViewScoped with the test app on forcing GC from within the NetBeans Profiler (with 1 omnifaces view scoped bean left per open tab).
When using JVisualVM attached to GlassFish/Payara I am getting references still held (even after #PreDestroy called) by field sessionListeners of type com.sun.web.server.WebContainerListener within ContainerBase$ContainerBackgroundProcessor, and they won't GC.
The image shows a screenshot of JVisualVM attached to Payara with only 1 tab open but still 9 OmniViewBean instances held, no matter how often GC is forced.
Updated results table using Mojarra-2.3.0 vs OmniFaces-2.6.6 in NetBeans IDE 8.2 Profiler
Updated test app sequence:

JSF2 LogicalViews - Losing the Current Page From the Map

We recently updated our Application to use JSF 2.1.3
We have an existing page that has three framesets (left nav, main, footer). Clicking on something in the main frame, causes the footer frame to be reloaded with new content. Because of this, we are losing the main frame from the logical view map after (eg. if com.sun.faces.numberOfLogicalViews is set to 10, we lose the main frame after 9 clicks). After which, trying to submit the main frame causes it to re-render, skipping the action method it's bound too.
Is there any way to keep that logical view from being removed from the map without increasing the number of logical views? I'm not sure why, but this behavior didn't occur when out app was using JSF 1.2.
We solved this problem by overriding the StateManagerImpl, and storing this particular view client side, instead of server side.
It appears in previous versions of jsf, you only needed to override the isClientSide method, but you now need to override the methods to write and restore state.

Whats the best way of sending parameters between pages?

We are using JSF in our project (im pretty new to it) were every page have a back bean Java file.
In order to move (redirect) from one page to another, i need to put all the parameters (search criteria) in the request scope before redirecting and then retrieve it back in the next page constructor. When you have few pages deep and you want to come back to the top, it becomes really annoying to maintain.
For example, if i have page 1 with advanced search filters, which redirects to page 2, depending on the chosen item, and from page 2, you get another list were you can go to page 3 for details. Now each time i need to put all the params in the request scope/read them again, store them in hidden fields and get them back.
Whats exactly wrong with this method and whats a better way to do it in JSF?
EDIT: the environment is IBM Rational Application Developer (RAD), which have its own JSF implementation. Not sure if that makes a difference.
Putting request scoped data in session scope will bite you (very) hard if you're going to open the same page in multiple windows/tabs. Only use the session scope if the data itself is also really session scoped (excellent examples are the "logged-in user" and the "shopping cart", you want it to be exactly the same throughout the entire session). Again, don't put request scoped data in the session scope. It hurts both you and the enduser.
Just design your beans smart (it makes no sense to have different beans containing the same data) and make use of h:inputHidden where needed, if necessary in combination with managed property injection. It's indeed a bit a pain to code and maintain. You can on the other hand also just grab Tomahawk <t:saveState> if the to-be-passed data is actually as big as a "whole" managed bean. It costs only a single line in the JSF page and has always been of great assistance.
*For example, if i have page 1 with advanced search filters, which redirects to page 2, depending on the chosen item, and from page 2, you get another list were you can go to page 3 for details. Now each time i need to put all the params in the request scope/read them again, store them in hidden fields and get them back.
Whats exactly wrong with this method and whats a better way to do it in JSF?*
There's nothing wrong with this method. Maybe you coded it the wrong way which caused that it looks unnecessarily overcomplicated. I can't tell much as long as you don't post details about the code used.
As per your edit:
EDIT: the environment is IBM Rational Application Developer (RAD), which have its own JSF implementation. Not sure if that makes a difference.
This is not true. IBM doesn't have any JSF implementation. It has just a component library (the poorly maintained hx prefixed components, also known as "Faces Client Framework"). WSAD/RAD ships with Sun JSF RI (Mojarra) as standard JSF implementation, although it's usually a heavily outdated version. Ensure that you keep it updated.
I'm only starting out with JSF too to be honest, but I thought you can save managed beans in the session scope, thus being able to access the bean on each request? You can also save the state client-side avoiding nastiness about session stickyness and stuff.
So you could save the data you are currently passing as request parameters in a session-scoped managed bean, and it will be available to any requests in that user's session, destroyed when the session times out or is deliberately invalidated (say on user logout).
I don't think JSF currently supports conversation state which I think might be the exact solution to your problem, maybe a session scoped managed bean would be the pragmatic solution?
Make your managed-bean session scoped.
If you are using MyFaces you can use PageFlowScope. If using Seam then use Conversation scope.
If pageflowscope or conversation scope is not available, then use session scoped beans. In addition you can use PhaseListener to initialize or execute specific methods before the page gets called. In you case if the flow is page1 -> page2 -> page3, then initialize the session scoped bean in PhaseListener if page1 gets called.
I'll update with more info if you need.

Resources