编辑:这个问题引起的问题在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:link来获取新页面。
  • 使用h:outputLink(或HTML A标签)来获取新页面。
  • 使用RELOAD命令或按钮在浏览器中重新加载页面。
  • 使用浏览器URL(也是GET)上的键盘ENTER重新加载页面。

  • 相比之下,通过使用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
  • 卸载 View 处理程序: OmniViewHandler#unloadView()
  • JSF View 状态破坏者: 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将失败:
  • 将物理 View 的数量设置为3(在Mojarra中,使用com.sun.faces.numberOfLogicalViews上下文参数,在MyFaces中使用org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION上下文参数)。
  • 创建一个引用标准JSF @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之前不会出现。

    10-01 17:37