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

本文介绍了如何检测和删除(在会话期间)无法被垃圾收集的未使用的 @ViewScoped bean的处理方法,对大家解决问题具有一定的参考价值

问题描述

codebulb.ch 在这篇文章中很好地解释和确认了这个问题提出的问题,包括 JSF @ViewScoped、CDI @ViewSCoped 之间的一些比较,以及 Omnifaces @ViewScoped,以及 JSF @ViewScoped 是设计泄漏"的明确声明: 中实现,已充实在其文档如下:

<块引用>

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

自 OmniFaces 2.3 起,卸载得到了进一步改进,在服务器端状态保存的情况下,还从 JSF 实现的内部 LRU 映射中物理删除了关联的 JSF 视图状态,从而进一步降低了 ViewExpiredException 的风险在之前创建/打开的其他视图上.作为此更改的副作用,在与 OmniFaces CDI 视图范围 bean 相同的视图中引用的任何标准 JSF 视图范围 bean 的 @PreDestroy 注释方法也将保证在浏览器卸载时被调用.

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

卸载脚本将运行 窗口的 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";如果(navigator.sendBeacon){//在卸载事件期间不推荐使用同步 XHR,现代浏览器为此提供了 Beacon API,它基本上会触发并忘记请求.navigator.sendBeacon(url, new Blob([query], {type: contentType}));}别的 {var xhr = new XMLHttpRequest();xhr.open(POST", url, false);xhr.setRequestHeader(X-Requested-With", XMLHttpRequest");xhr.setRequestHeader(Content-Type", contentType);xhr.send(查询);}

卸载视图处理程序将 明确销毁所有 @ViewScoped bean,包括标准 JSF bean(请注意,卸载脚本仅在视图引用至少一个 OmniFaces @ViewScoped bean).

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

然而,这不会破坏 HTTP 会话中的物理 JSF 视图状态,因此下面的 使用case 会失败:

  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 视图状态是特定于实现的.这至少解释了 Hacks 类中应该实现这一点的相当hacky 的代码.

这个特定案例的集成测试可以在ViewScopedIT#destroyViewState()ViewScopedIT.xhtml目前针对 WildFly 10.0.0、TomEE 7.0.1 和 Payara 运行4.1.1.163.


简而言之:只需将 javax.faces.view.ViewScoped 替换为 org.omnifaces.cdi.ViewScoped.其余的都是透明的.

import javax.inject.Named;导入 org.omnifaces.cdi.ViewScoped;@命名@ViewScoped公共类 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:

There may be cases when it's desirable to immediately destroy a view scoped bean as well when the browser unload event is invoked. I.e. when the user navigates away by GET, or closes the browser tab/window. None of the both JSF 2.2 view scope annotations support this. Since OmniFaces 2.2, this CDI view scope annotation will guarantee that the @PreDestroy annotated method is also invoked on browser unload. This trick is done by a synchronous XHR request via an automatically included helper script omnifaces:unload.js. There's however a small caveat: on slow network and/or poor server hardware, there may be a noticeable lag between the enduser action of unloading the page and the desired result. If this is undesireable, then better stick to JSF 2.2's own view scope annotations and accept the postponed destroy.

Since OmniFaces 2.3, the unload has been further improved to also physically remove the associated JSF view state from JSF implementation's internal LRU map in case of server side state saving, hereby further decreasing the risk at ViewExpiredException on the other views which were created/opened earlier. As side effect of this change, the @PreDestroy annotated method of any standard JSF view scoped beans referenced in the same view as the OmniFaces CDI view scoped bean will also guaranteed be invoked on browser unload.

You can find the relevant source code here:

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:

  1. Set number of physical views to 3 (in Mojarra, use com.sun.faces.numberOfLogicalViews context param and in MyFaces use org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION context param).
  2. Create a page which references a standard JSF @ViewScoped bean.
  3. Open this page in a tab and keep it open all time.
  4. Open the same page in another tab and then immediately close this tab.
  5. Open the same page in another tab and then immediately close this tab.
  6. Open the same page in another tab and then immediately close this tab.
  7. 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的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,WP2

admin_action_{$_REQUEST[‘action’]}

do_action( "admin_action_{$_REQUEST[‘action’]}" )动作钩子::在发送“Action”请求变量时激发。Action Hook: Fires when an ‘action’ request variable is sent.目录锚点:#说明#源码说明(Description)钩子名称的动态部分$_REQUEST['action']引用从GET或POST请求派生的操作。源码(Source)更新版本源码位置使用被使用2.6.0 wp-admin/admin.php:...

日期:2020-09-02 17:44:16 浏览:1127

admin_footer-{$GLOBALS[‘hook_suffix’]}

do_action( "admin_footer-{$GLOBALS[‘hook_suffix’]}", string $hook_suffix )操作挂钩:在默认页脚脚本之后打印脚本或数据。Action Hook: Print scripts or data after the default footer scripts.目录锚点:#说明#参数#源码说明(Description)钩子名的动态部分,$GLOBALS['hook_suffix']引用当前页的全局钩子后缀。参数(Parameters)参数类...

日期:2020-09-02 17:44:20 浏览:1032

customize_save_{$this->id_data[‘base’]}

do_action( "customize_save_{$this-&gt;id_data[‘base’]}", WP_Customize_Setting $this )动作钩子::在调用WP_Customize_Setting::save()方法时激发。Action Hook: Fires when the WP_Customize_Setting::save() method is called.目录锚点:#说明#参数#源码说明(Description)钩子名称的动态部分,$this->id_data...

日期:2020-08-15 15:47:24 浏览:775

customize_value_{$this->id_data[‘base’]}

apply_filters( "customize_value_{$this-&gt;id_data[‘base’]}", mixed $default )过滤器::过滤未作为主题模式或选项处理的自定义设置值。Filter Hook: Filter a Customize setting value not handled as a theme_mod or option.目录锚点:#说明#参数#源码说明(Description)钩子名称的动态部分,$this->id_date['base'],指的是设置...

日期:2020-08-15 15:47:24 浏览:866

get_comment_author_url

过滤钩子:过滤评论作者的URL。Filter Hook: Filters the comment author’s URL.目录锚点:#源码源码(Source)更新版本源码位置使用被使用 wp-includes/comment-template.php:32610...

日期:2020-08-10 23:06:14 浏览:903

network_admin_edit_{$_GET[‘action’]}

do_action( "network_admin_edit_{$_GET[‘action’]}" )操作挂钩:启动请求的处理程序操作。Action Hook: Fires the requested handler action.目录锚点:#说明#源码说明(Description)钩子名称的动态部分$u GET['action']引用请求的操作的名称。源码(Source)更新版本源码位置使用被使用3.1.0 wp-admin/network/edit.php:3600...

日期:2020-08-02 09:56:09 浏览:848

network_sites_updated_message_{$_GET[‘updated’]}

apply_filters( "network_sites_updated_message_{$_GET[‘updated’]}", string $msg )筛选器挂钩:在网络管理中筛选特定的非默认站点更新消息。Filter Hook: Filters a specific, non-default site-updated message in the Network admin.目录锚点:#说明#参数#源码说明(Description)钩子名称的动态部分$_GET['updated']引用了非默认的...

日期:2020-08-02 09:56:03 浏览:834

pre_wp_is_site_initialized

过滤器::过滤在访问数据库之前是否初始化站点的检查。Filter Hook: Filters the check for whether a site is initialized before the database is accessed.目录锚点:#源码源码(Source)更新版本源码位置使用被使用 wp-includes/ms-site.php:93910...

日期:2020-07-29 10:15:38 浏览:809

WordPress 的SEO 教学:如何在网站中加入关键字(Meta Keywords)与Meta 描述(Meta Description)?

你想在WordPress 中添加关键字和meta 描述吗?关键字和meta 描述使你能够提高网站的SEO。在本文中,我们将向你展示如何在WordPress 中正确添加关键字和meta 描述。为什么要在WordPress 中添加关键字和Meta 描述?关键字和说明让搜寻引擎更了解您的帖子和页面的内容。关键词是人们寻找您发布的内容时,可能会搜索的重要词语或片语。而Meta Description则是对你的页面和文章的简要描述。如果你想要了解更多关于中继标签的资讯,可以参考Google的说明。Meta 关键字和描...

日期:2020-10-03 21:18:25 浏览:1620

谷歌的SEO是什么

SEO (Search Engine Optimization)中文是搜寻引擎最佳化,意思近于「关键字自然排序」、「网站排名优化」。简言之,SEO是以搜索引擎(如Google、Bing)为曝光媒体的行销手法。例如搜寻「wordpress教学」,会看到本站的「WordPress教学:12个课程…」排行Google第一:关键字:wordpress教学、wordpress课程…若搜寻「网站架设」,则会看到另一个网页排名第1:关键字:网站架设、架站…以上两个网页,每月从搜寻引擎导入自然流量,达2万4千:每月「有机搜...

日期:2020-10-30 17:23:57 浏览:1263