编辑:这个问题引起的问题在codebulb.ch上得到了很好的解释和确认,包括JSF @ViewScoped
,CDI @ViewSCoped
和Omnifaces @ViewScoped
之间的一些比较,并明确声明JSF @ViewScoped
是“设计泄漏的”。 :May 24, 2015 Java EE 7 Bean scopes compared part 2 of 2
编辑:2017-12-05用于此问题的测试用例仍然非常有用,但是原始帖子(和图像)中有关垃圾回收的结论基于JVisualVM,从那以后我发现它们无效。 改用NetBeans Profiler! 我现在通过测试应用程序从NetBeans Profiler内强制执行GC,而不是附加到GlassFish/Payara的JVisualVM上,获得与OmniFaces ViewScoped完全一致的结果,在这里我仍通过字段sessionListeners
保留引用(即使在调用@PreDestroy之后)在com.sun.web.server.WebContainerListener
中输入ContainerBase$ContainerBackgroundProcessor
,它们不会GC。
众所周知,在JSF2.2中,对于使用@ViewScoped bean的页面,使用以下任何一种技术进行导航(或重新加载)都会导致@ViewScoped bean实例在 session 中“晃动”,因此它不会被垃圾收集,导致堆内存不断增长(只要由GETs引起):
相比之下,通过使用h:commandButton通过JSF导航系统将导致释放@ViewScoped bean,以便可以对其进行垃圾回收。
这是由BalusC在JSF 2.1 ViewScopedBean @PreDestroy method is not called上解释的,并由我在https://stackoverflow.com/a/30410401/679457上的小型NetBeans示例项目针对JSF2.2和Mojarra 2.2.9进行了演示,该项目说明了各种导航情况,并且是available for download here。 (编辑:2015-05-28:现在也可以在下面找到完整的代码。)
[编辑:2016-11-13现在,还有一个改进的测试Web应用程序,具有完整的说明并与GitHub上的OmniFaces
@ViewScoped
和结果表进行比较:https://github.com/webelcomau/JSFviewScopedNav]我在这里重复index.html的图像,该图像总结了导航情况和堆内存的结果:
问:如何检测由GET导航引起的此类“悬挂/悬挂” @ViewScoped bean并将其删除,或者以其他方式使其变为可垃圾回收?
请注意,我不是在问 session 结束时如何清理它们,我已经看到了各种解决方案,我正在寻找在 session 期间清理它们的方法,以使堆内存在 session 期间不会过度增长由于意外的GET导航。
最佳答案
基本上,您希望在窗口卸载期间销毁JSF View 状态和所有 View 作用域的bean。该解决方案已在OmniFaces @ViewScoped
annotation中实现,该文件的文档如下所示:
您可以在此处找到相关的源代码:
ViewScopeManager#registerUnloadScript()
unload.unminified.js
OmniViewHandler#unloadView()
Hacks#removeViewState()
卸载脚本将运行during窗口的
beforeunload
事件,除非由任何基于JSF的(ajax)表单提交引起。对于命令链接和/或ajax提交,这是特定于实现的。认识到Currently Mojarra,MyFaces和PrimeFaces。卸载脚本在现代浏览器上将为trigger
navigator.sendBeacon
并回退到同步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) {
// 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);
}
卸载 View 处理程序将explicitly销毁所有@ViewScoped
Bean,包括标准的JSF Bean(请注意,仅当 View 引用至少一个OmniFaces @ViewScoped
Bean时,才会初始化卸载脚本)。context.getApplication().publishEvent(context, PreDestroyViewMapEvent.class, UIViewRoot.class, createdView);
但是,这不会破坏HTTP session 中的物理JSF View 状态,因此下面的use case将失败:com.sun.faces.numberOfLogicalViews
上下文参数,在MyFaces中使用org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION
上下文参数)。 @ViewScoped
bean的页面。 这将导致
ViewExpiredException
失败,因为在PreDestroyViewMapEvent
期间不会物理破坏先前关闭的选项卡的JSF View 状态。他们仍然在 session 中坚持不懈。 OmniFaces @ViewScoped
实际上将销毁它们。但是销毁JSF View 状态是特定于实现的。这至少解释了Hacks
类中相当hacky的代码,应该可以实现该目的。可以在
ViewScopedIT#destroyViewState()
上的 ViewScopedIT.xhtml
中找到针对此特定情况的集成测试,这是针对WildFly 10.0.0,TomEE 7.0.1和Payara 4.1.1.163运行的currently。简而言之:只需将
javax.faces.view.ViewScoped
替换为org.omnifaces.cdi.ViewScoped
即可。其余的是透明的。import javax.inject.Named;
import org.omnifaces.cdi.ViewScoped;
@Named
@ViewScoped
public class Bean implements Serializable {}
我至少已努力propose一个公共(public)API方法来物理破坏JSF View 状态。也许它将在JSF 2.3中出现,然后我应该能够消除OmniFaces Hacks
类中的样板。一旦在OmniFaces中对它进行了完善,它最终可能会在JSF中出现,但在2.4之前不会出现。