如何检测和删除(在会话期间)无法进行垃圾收集的未使用的 @ViewScoped beans

2024-01-05

编辑:这个问题提出的问题在 codebulb.ch 的这篇文章中得到了很好的解释和证实,包括 JSF 之间的一些比较@ViewScoped, CDI @ViewSCoped,以及全能面孔@ViewScoped,并明确声明 JSF@ViewScoped是“设计泄漏”:2015 年 5 月 24 日 Java EE 7 Bean 范围比较第 2 部分(共 2 部分) http://www.codebulb.ch/2015/05/java-ee-7-bean-scopes-compared-part-2.html


编辑:2017-12-05 用于此问题的测试用例仍然非常有用,但是原始帖子(和图像)中有关垃圾收集的结论是基于 JVisualVM 的,此后我发现它们无效。请改用 NetBeans Profiler!我现在得到了 OmniFaces ViewScoped 与测试应用程序完全一致的结果,该测试应用程序强制从 NetBeans Profiler 中进行 GC,而不是附加到 GlassFish/Payara 的 JVisualVM,在其中我得到的引用仍然按字段保留(即使在 @PreDestroy 调用之后)sessionListeners类型的com.sun.web.server.WebContainerListener within ContainerBase$ContainerBackgroundProcessor,而且他们不会 GC。


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

  • 使用 h:link 获取新页面。

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

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

  • 使用浏览器 URL 上的键盘 ENTER(也是 GET)重新加载页面。

相比之下,使用 h:commandButton 传递 JSF 导航系统会导致 @ViewScoped bean 的释放,以便可以对其进行垃圾收集。

(BalusC)对此进行了解释JSF 2.1 ViewScopedBean @PreDestroy 方法未调用 https://stackoverflow.com/questions/6726411/jsf-2-1-viewscopedbean-predestroy-method-is-not-called/30410401#30410401并通过我的小型 NetBeans 示例项目演示了 JSF2.2 和 Mojarra 2.2.9https://stackoverflow.com/a/30410401/679457 https://stackoverflow.com/a/30410401/679457,该项目说明了各种导航案例,并且是可以在这里下载 http://www.webel.com.au/downloads/jsf/JSFviewScopedNav-2015-05-23.tgz. (编辑:2015-05-28:完整的代码现在也可以在下面找到。)

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

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

问:如何检测由 GET 导航引起的此类“悬挂/悬挂”@ViewScoped beans 并将其删除,或者以其他方式使它们可垃圾收集?

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



基本上,您希望在窗口卸载期间销毁 JSF 视图状态和所有视图范围的 bean。该解决方案已实施于OmniFaces@ViewScoped注解 http://showcase.omnifaces.org/cdi/ViewScoped它在其文档中充实如下:

在某些情况下,可能需要立即销毁视图作用域 bean 以及浏览器unload事件被调用。 IE。当用户通过 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 的带注释方法也将保证在浏览器卸载时被调用。

您可以在这里找到相关的源代码:

  • 卸载脚本初始化程序:ViewScopeManager#registerUnloadScript() https://github.com/omnifaces/omnifaces/blob/2.6.6/src/main/java/org/omnifaces/cdi/viewscope/ViewScopeManager.java#L205
  • 卸载脚本本身:unload.unminified.js https://github.com/omnifaces/omnifaces/blob/2.6.6/src/main/resources/META-INF/resources/omnifaces/unload.unminified.js
  • 卸载视图处理程序:OmniViewHandler#unloadView() https://github.com/omnifaces/omnifaces/blob/2.6.6/src/main/java/org/omnifaces/viewhandler/OmniViewHandler.java#L127
  • JSF 视图状态驱逐舰:Hacks#removeViewState() https://github.com/omnifaces/omnifaces/blob/2.6.6/src/main/java/org/omnifaces/util/Hacks.java#L417

卸载脚本将运行during https://github.com/omnifaces/omnifaces/blob/2.6.6/src/main/resources/META-INF/resources/omnifaces/unload.unminified.js#L61视窗beforeunload event, unless这是由任何基于 JSF (ajax) 的表单提交引起的。至于 commandlink 和/或 ajax 提交,这是特定于实现的。现在 https://github.com/omnifaces/omnifaces/blob/2.6.6/src/main/resources/META-INF/resources/omnifaces/util.unminified.js#L71Mojarra、MyFaces 和 PrimeFaces 均得到认可。

卸载脚本将trigger https://github.com/omnifaces/omnifaces/blob/2.6.6/src/main/resources/META-INF/resources/omnifaces/unload.unminified.js#L72 navigator.sendBeacon https://developer.mozilla.org/en-US/docs/Web/API/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);
}

卸载视图处理程序将明确地 https://github.com/omnifaces/omnifaces/blob/2.6.6/src/main/java/org/omnifaces/viewhandler/OmniViewHandler.java#L133摧毁所有@ViewScopedbeans,包括标准 JSF 的(请注意,仅当视图引用至少一个 OmniFaces 时,才会初始化卸载脚本@ViewScoped bean).

context.getApplication().publishEvent(context, PreDestroyViewMapEvent.class, UIViewRoot.class, createdView);

然而,这不会破坏 HTTP 会话中的物理 JSF 视图状态,因此如下所示use case https://github.com/omnifaces/omnifaces/issues/208会失败:

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

这会失败ViewExpiredException因为之前关闭的选项卡的 JSF 视图状态在PreDestroyViewMapEvent。他们仍然留在会议中。 OmniFaces@ViewScoped实际上会摧毁他们。然而,销毁 JSF 视图状态是特定于实现的。这至少解释了其中相当hacky的代码Hacks应该实现这一目标的类。

此特定案例的集成测试可以在ViewScopedIT#destroyViewState() https://github.com/omnifaces/omnifaces/blob/2.6.6/src/test/java/org/omnifaces/test/cdi/viewscoped/ViewScopedIT.java#L178 on ViewScopedIT.xhtml https://github.com/omnifaces/omnifaces/blob/2.6.6/src/test/resources/org.omnifaces.test.cdi.viewscoped/ViewScopedIT.xhtml这是现在 https://github.com/omnifaces/omnifaces/blob/2.6.6/pom.xml#L74针对 WildFly 10.0.0、TomEE 7.0.1 和 Payara 4.1.1.163 运行。


简而言之:只需更换javax.faces.view.ViewScoped by org.omnifaces.cdi.ViewScoped。其余部分是透明的。

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

@Named
@ViewScoped
public class Bean implements Serializable {}

我至少已经努力了propose http://download.oracle.com/javaee-archive/javaserverfaces-spec-public.java.net/jsr372-experts/2016/07/1091.html用于物理破坏 JSF 视图状态的公共 API 方法。也许它会出现在 JSF 2.3 中,然后我应该能够消除 OmniFaces 中的样板文件Hacks班级。一旦在 OmniFaces 中完善,它可能最终会出现在 JSF 中,但不会早于 2.4。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何检测和删除(在会话期间)无法进行垃圾收集的未使用的 @ViewScoped beans 的相关文章

随机推荐