如何检测和删除(在会话期间)未使用垃圾的未使用的@ViewScoped bean

Web*_*ter 10 jsf garbage-collection heap-memory jsf-2 view-scope

编辑:这个问题提出的问题在codebulb.ch的这篇文章中得到了很好的解释和证实,包括JSF @ViewScoped,CDI @ViewSCoped和Omnifaces 之间的一些比较@ViewScoped,以及JSF @ViewScoped"设计漏洞" 的明确声明:5月24日, 2015 Java EE 7 Bean范围比较了2的第2部分


编辑:2017-12-05用于这个问题的测试用例仍然非常有用,但原始帖子(和图像)中关于垃圾收集的结论是基于JVisualVM,我发现它们无效.请改用NetBeans Profiler!现在我正在从NetBeans配置,而不是JVisualVM连接到GlassFish /似鲭水狼牙鱼,在这里我感到场得到引用(甚至@PreDestroy称为后)仍持有中强制GC的OmniFaces ViewScoped完全一致的结果与试验应用sessionListeners型的com.sun.web.server.WebContainerListenerContainerBase$ContainerBackgroundProcessor,他们不会GC.


众所周知,在JSF2.2中,对于使用@ViewScoped bean的页面,使用以下任何一种技术导航(或重新加载)将导致@ViewScoped bean在会话中"悬空"的实例它不会被垃圾收集,导致堆内存不断增长(只要GET引发):

  • 使用h:链接获取新页面.

  • 使用h:outputLink(或HTML A标记)获取新页面.

  • 使用RELOAD命令或按钮在浏览器中重新加载页面.

  • 使用键盘重新加载页面在浏览器URL上输入ENTER(也是GET).

相比之下,通过使用一个h:commandButton来传递JSF导航系统会导致@ViewScoped bean的发布,从而可以对其进行垃圾回收.

这是由(由BalusC)在JSF 2.1上解释的.ViewScopedBean @PreDestroy方法未被我的小型NetBeans示例项目/sf/answers/2128728101/在JSF2.2和Mojarra 2.2.9中调用和演示,其中项目说明了各种导航案例,可在此处下载.(编辑:2015-05-28:完整代码现在也可以在下面找到.)

[编辑:2016-11-13现在还有一个改进的测试网络应用程序,包含完整的说明,@ViewScoped并在GitHub上与OmniFaces 和结果表进行比较:https://github.com/webelcomau/JSFviewScopedNav]

我在这里重复一个index.html的图像,它总结了导航案例和堆内存的结果:

在此输入图像描述

问:如何检测由GET导航引起的这种"悬挂/悬挂"@ViewScoped bean并将其删除,或以其他方式呈现垃圾收集?

请注意,我不会在会话结束时询问如何清理它们,我已经看到了各种解决方案,我正在寻找在会话期间清理它们的方法,以便在会话期间堆内存不会过度增长由于无意中的GET导航.


Bal*_*usC 5

基本上,您希望在窗口卸载期间销毁JSF视图状态和所有视图范围的bean.该解决方案已在OmniFaces @ViewScoped注释中实现,注释在其文档中充实,如下所示:

可能存在在unload调用浏览器事件时立即销毁视图范围bean的情况.即用户通过GET导航或关闭浏览器选项卡/窗口时.两个JSF 2.2视图范围注释都不支持这一点.从OmniFaces 2.2开始,此CDI视图范围注释将保证@PreDestroy在浏览器卸载时也调用带注释的方法.这个技巧是通过自动包含的帮助程序脚本通过同步XHR请求完成的omnifaces:unload.js.然而,有一个小警告:在慢速网络和/或糟糕的服务器硬件上,卸载页面的最终用户操作与所需结果之间可能存在明显的滞后.如果这是不可取的,那么最好坚持使用JSF 2.2自己的视图范围注释并接受推迟的销毁.

从OmniFaces 2.3开始,卸载进一步改进,以便在服务器端状态保存的情况下从JSF实现的内部LRU映射中物理移除关联的JSF视图状态,从而进一步降低ViewExpiredException先前创建/打开的其他视图的风险.作为此更改的副作用,@PreDestroy在OmniFaces CDI视图作用域bean的同一视图中引用的任何标准JSF视图作用域bean 的带注释的方法也将保证在浏览器卸载时调用.

您可以在此处找到相关的源代码:

卸载脚本将窗口beforeunload事件期间运行,除非它是由任何基于JSF的(ajax)表单提交引起的.至于commandlink和/或ajax提交,这是特定于实现的.目前, Mojarra,MyFaces和PrimeFaces受到认可.

卸载脚本将在现代浏览器上触发 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);
}
Run Code Online (Sandbox Code Playgroud)

卸载视图处理程序将显式销毁所有@ViewScopedbean,包括标准的JSF(请注意,只有在视图引用至少一个OmniFaces @ViewScopedbean 时才会初始化卸载脚本).

context.getApplication().publishEvent(context, PreDestroyViewMapEvent.class, UIViewRoot.class, createdView);
Run Code Online (Sandbox Code Playgroud)

但是,这不会破坏HTTP会话中的物理JSF视图状态,因此以下用例将失败:

  1. 将物理视图的数量设置为3(在Mojarra中,使用com.sun.faces.numberOfLogicalViews上下文参数,在MyFaces中使用org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION上下文参数).
  2. 创建一个引用标准JSF @ViewScopedbean 的页面.
  3. 在选项卡中打开此页面并始终保持打开状态.
  4. 在另一个选项卡中打开同一页面,然后立即关闭此选项卡.
  5. 在另一个选项卡中打开同一页面,然后立即关闭此选项卡.
  6. 在另一个选项卡中打开同一页面,然后立即关闭此选项卡.
  7. 在第一个标签中提交表单.

这将失败,ViewExpiredException因为之前关闭的选项卡的JSF视图状态在物理上没有被破坏PreDestroyViewMapEvent.他们仍然坚持参加会议.OmniFaces @ViewScoped实际上会摧毁它们.但是,销毁JSF视图状态是特定于实现的.这至少解释了Hacks类中应该实现的相当hacky代码.

对于这种特殊情况下的集成测试,可以发现ViewScopedIT#destroyViewState()ViewScopedIT.xhtml目前对WildFly 10.0.0,TomEE 7.0.1和似鲭水狼牙鱼4.1.1.163运行.


简而言之:只需更换javax.faces.view.ViewScopedorg.omnifaces.cdi.ViewScoped.其余的是透明的.

import javax.inject.Named;
import org.omnifaces.cdi.ViewScoped;

@Named
@ViewScoped
public class Bean implements Serializable {}
Run Code Online (Sandbox Code Playgroud)

我至少努力提出一种公共API方法来物理破坏JSF视图状态.也许它会出现在JSF 2.3中,然后我应该能够消除OmniFaces Hacks类中的样板.一旦OmniFaces中的东西被抛光,它可能最终会出现在JSF中,但不会出现在2.4之前.

  • 我一定会在JSF.next中考虑它。我至少可以说,我从事过的几个生产应用程序从中受益匪浅。在大量使用本机JSF @ViewScoped的特定Web应用程序上,内存使用率甚至降低了80%。 (2认同)