问题描述
编辑:这个问题引发的问题在本文中由codebulb.ch很好地解释和确认,包括JSF @ViewScoped
,CDI <$ c之间的一些比较$ c> @ViewSCoped 和Omnifaces @ViewScoped
,以及JSF @ViewScoped
是'由设计泄漏': Hacks#removeViewState( )
卸载脚本将运行 beforeunload
事件,除非是由任何基于JSF(ajax)表单提交引起的。至于commandlink和/或ajax提交,这是特定于实现的。 Mojarra,MyFaces和PrimeFaces被认可。 卸载脚本将 并返回到同步XHR(异步会失败,因为页面可能会比请求实际命中服务器更快地卸载)。
var url = form.action;
var query =omnifaces.event = unload& id =+ id +& + VIEW_STATE_PARAM +=+ encodeURIComponent(form [VIEW_STATE_PARAM] .value);
var contentType =application / x-www-form-urlencoded;
if(navigator.sendBeacon){
//在卸载事件中不推荐使用同步XHR,现代浏览器为此提供了Beacon API,它基本上会启动并忘记请求。
navigator.sendBeacon(url,new Blob([query],{type:contentType}));
}
else {
var xhr = new XMLHttpRequest();
xhr.open(POST,url,false);
xhr.setRequestHeader(X-Requested-With,XMLHttpRequest);
xhr.setRequestHeader(Content-Type,contentType);
xhr.send(query);
}
卸载视图处理程序将销毁所有 @ViewScoped
bean,包括标准的JSF bean(请注意,只有在视图引用至少一个OmniFaces @ViewScoped
bean时,才会初始化卸载脚本) 。
context.getApplication()。publishEvent(context,PreDestroyViewMapEvent.class,UIViewRoot.class,createdView);
然而,这不会破坏HTTP会话中的物理JSF视图状态,因此不会破坏 will fail:
- 将物理视图的数量设置为3(在Mojarra中,使用
com.sun.faces.numberOfLogicalViews
context param并在MyFaces中使用org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION
context param)。
- 创建一个引用标准JSF
@ViewScoped $ c的页面
- 在$选项卡中打开此页面,并始终保持打开状态。
- 在另一个选项卡中打开同一页面,然后立即关闭此标签。
- 打开另一个标签中的同一页面,然后立即关闭此标签。
- 在另一标签中打开同一页面,然后立即关闭此标签。
- 在第一个标签中提交表单。
一个 ViewE xpiredException
,因为在 PreDestroyViewMapEvent
期间,以前关闭的选项卡的JSF视图状态不会被物理破坏。他们仍然坚持在会议中。 OmniFaces @ViewScoped
实际上会销毁它们。然而,销毁JSF视图状态是特定于实现的。这至少解释了在 Hacks
类中相当不好用的代码,它应该实现这一点。
这个特定的集成测试大小写可以在关于这是针对WildFly 10.0.0运行, TomEE 7.0.1和Payara 4.1.1.163。
简而言之:只需替换 javax。 faces.view.View.Scoped
由 org.omnifaces.cdi.ViewScoped
。其余部分是透明的。
import javax.inject.Named;
import org.omnifaces.cdi.ViewScoped;
@Named
@ViewScoped
public class Bean实现Serializable {}
我至少已经努力了一种公开的API方法来物理销毁JSF视图状态。也许它会出现在JSF 2.3中,然后我应该能够消除OmniFaces Hacks
类中的样板。一旦这些东西在OmniFaces中被抛光,它最终可能会以JSF的形式出现,但不会在2.4之前。
EDIT: The problem raised by this question is very well explained and confirmed in this article by codebulb.ch, including some comparison between JSF @ViewScoped
, CDI @ViewSCoped
, and the Omnifaces @ViewScoped
, and a clear statement that JSF @ViewScoped
is 'leaky by design': May 24, 2015 Java EE 7 Bean scopes compared part 2 of 2
EDIT: 2017-12-05 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 instead of JVisualVM attached to GlassFish/Payara, where 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.
It is known that in JSF2.2, for a page that uses a @ViewScoped bean, navigating away from it (or reloading it) using any of the following techniques will result in instances of the @ViewScoped bean "dangling" in the session so that it will not be garbage collected, leading to endlessly growing heap memory (as long as provoked by GETs):
Using an h:link to GET a new page.
Using an h:outputLink (or an HTML A tag) to GET a new page.
Reloading the page in the browser using a RELOAD command or button.
Reloading the page using a keyboard ENTER on the browser URL (also a GET).
By contrast, passing through the JSF navigation system by using say an h:commandButton results in the release of the @ViewScoped bean such that it can be garbage collected.
This is explained (by BalusC) at JSF 2.1 ViewScopedBean @PreDestroy method is not called and demonstrated for JSF2.2 and Mojarra 2.2.9 by my small NetBeans example project at https://stackoverflow.com/a/30410401/679457, which project illustrates the various navigation cases and is available for download here. (EDIT: 2015-05-28: The full code is now also available here below.)
[EDIT: 2016-11-13 There is now also an improved test web app with full instructions and comparison with OmniFaces @ViewScoped
and result table on GitHub here: https://github.com/webelcomau/JSFviewScopedNav]
I repeat here an image of the index.html, which summarises the navigation cases and the results for heap memory:
Q: How can I detect such "hanging/dangling" @ViewScoped beans caused by GET navigations and remove them, or otherwise render them garbage collectable ?
Please note that I am not asking how to clean them up when the session ends, I have already seen various solutions for that, I am looking for ways to clean them up during a session, so that heap memory does not grow excessively during a session due to inadvertent GET navigations.
Basically, you want the JSF view state and all view scoped beans to be destroyed during a window unload. The solution has been implemented in OmniFaces @ViewScoped
annotation which is fleshed out in its documentation as below:
You can find the relevant source code here:
- The unload script initializer:
ViewScopeManager#registerUnloadScript()
- The unload script itself:
unload.unminified.js
- The unload view handler:
OmniViewHandler#unloadView()
- The JSF view state destroyer:
Hacks#removeViewState()
The unload script will run during window's beforeunload
event, unless it's caused by any JSF based (ajax) form submit. As to commandlink and/or ajax submits, this is implementation specific. Currently Mojarra, MyFaces and PrimeFaces are recognized.
The unload script will trigger navigator.sendBeacon
on modern browsers and fall back to synchronous XHR (asynchronous would fail as page might be unloaded sooner than the request actually hits the server).
var url = form.action;
var query = "omnifaces.event=unload&id=" + id + "&" + VIEW_STATE_PARAM + "=" + encodeURIComponent(form[VIEW_STATE_PARAM].value);
var contentType = "application/x-www-form-urlencoded";
if (navigator.sendBeacon) {
// Synchronous XHR is deprecated during unload event, modern browsers offer Beacon API for this which will basically fire-and-forget the request.
navigator.sendBeacon(url, new Blob([query], {type: contentType}));
}
else {
var xhr = new XMLHttpRequest();
xhr.open("POST", url, false);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.setRequestHeader("Content-Type", contentType);
xhr.send(query);
}
The unload view handler will explicitly destroy all @ViewScoped
beans, including standard JSF ones (do note that the unload script is only initialized when the view references at least one OmniFaces @ViewScoped
bean).
context.getApplication().publishEvent(context, PreDestroyViewMapEvent.class, UIViewRoot.class, createdView);
This however doesn't destroy the physical JSF view state in the HTTP session and thus the below use case would fail:
- Set number of physical views to 3 (in Mojarra, use
com.sun.faces.numberOfLogicalViews
context param and in MyFaces useorg.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION
context param). - Create a page which references a standard JSF
@ViewScoped
bean. - Open this page in a tab and keep it open all time.
- Open the same page in another tab and then immediately close this tab.
- Open the same page in another tab and then immediately close this tab.
- Open the same page in another tab and then immediately close this tab.
- Submit a form in the first tab.
This would fail with a ViewExpiredException
because the JSF view states of previously closed tabs aren't physically destroyed during PreDestroyViewMapEvent
. They still stick around in the session. OmniFaces @ViewScoped
will actually destroy them. Destroying the JSF view state is however implementation specific. That explains at least the quite hacky code in Hacks
class which should achieve that.
The integration test for this specific case can be found in ViewScopedIT#destroyViewState()
on ViewScopedIT.xhtml
which is currently run against WildFly 10.0.0, TomEE 7.0.1 and Payara 4.1.1.163.
In a nutshell: just replace javax.faces.view.ViewScoped
by org.omnifaces.cdi.ViewScoped
. The rest is transparent.
import javax.inject.Named;
import org.omnifaces.cdi.ViewScoped;
@Named
@ViewScoped
public class Bean implements Serializable {}
I have at least made an effort to propose a public API method to physically destroy the JSF view state. Perhaps it will come in JSF 2.3 and then I should be able to eliminate the boilerplate in OmniFaces Hacks
class. Once the thing is polished in OmniFaces, it will perhaps ultimately come in JSF, but not before 2.4.
这篇关于如何检测和删除(在会话期间)不能被垃圾收集的未使用的@ViewScoped bean的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!