JSF 2.2 内存消耗:为啥 Mojarra 将最后 25 个视图的 ViewScoped Beans 保留在内存中?

Posted

技术标签:

【中文标题】JSF 2.2 内存消耗:为啥 Mojarra 将最后 25 个视图的 ViewScoped Beans 保留在内存中?【英文标题】:JSF 2.2 Memory Consumption: Why does Mojarra keep the ViewScoped Beans of the last 25 Views in Memory?JSF 2.2 内存消耗:为什么 Mojarra 将最后 25 个视图的 ViewScoped Beans 保留在内存中? 【发布时间】:2015-11-04 18:05:16 【问题描述】:

每个会话的内存增长

我们在使用 JSF 2.2 (2.2.12) 和 Mojarra 时遇到了高内存消耗。在调查了我们的负载测试后,我们发现我们的 ViewScoped Bean 中的数据量非常大(有时超过 1MB)。无论如何 - 当从一个视图导航到另一个视图时,会话内存大小会越来越大。我们不能在短期内减小 bean 的大小,所以这种行为会产生相当大的影响。

解决方案 1 - 更改上下文参数(不起作用)

现在 - 我们使用 Mojarra 的官方上下文参数,默认设置为 15:

com.sun.faces.numberOfLogicalViews
com.sun.faces.numberOfViewsInSession

将这些参数更改为较低的值不会对负载测试中的内存消耗产生任何影响。

解决方案 2 - 更改 activeViewMapsSize(工作)

我们在调试 Mojarra 时,在ViewScopeManager 中发现了以下代码:

Integer size = (Integer) sessionMap.get(ACTIVE_VIEW_MAPS_SIZE);
if (size == null) 
    size = 25;

保留上次访问的视图的默认大小似乎是 25。看到这一点,我们实现了一个会话监听器,将这个值设置为 1:

public class SetActiveViewMapsSizeSessionListener implements HttpSessionListener 
    @Override
    public void sessionCreated(HttpSessionEvent event) 
        event.getSession().setAttribute(ViewScopeManager.ACTIVE_VIEW_MAPS_SIZE, 1);
    

这显然奏效了。由于只保留了 1 个视图,因此内存停止增长。

那么为什么内存中有 25 个视图?

所以 Mojarra 会在内存中保留 25 个视图的历史记录,以防 Session 中没有定义不同的值。我找不到任何关于此的文档。有人可以解释这是为了什么吗?是为了浏览器返回吗?我们在 JSF 页面上禁用了缓存。所以浏览器返回总是会创建一个新的视图。这对我们来说应该不是问题。

解决方案 2 是否有效?有人可以解释这种方法的缺点吗?

更新 1

经过各种cmet和更深的调试,结果是:

com.sun.faces.numberOfLogicalViews 定义了logicalViewMap的大小,它只存储(!)ui组件树的状态 com.sun.faces.application.view.activeViewMapsSize 定义了包含 ViewScoped bean 的 activeViewMap 的大小

numberOfLogicalViews 更改为 1 时,mojarra 仍将跟踪最近 25 个视图的所有视图范围 bean。当您以相反的方式配置它时 - numberOfLogicalViews 到 15 和 activeViewMapsSize 到 1 - 我猜由于缺少数据,视图无法正确初始化。我们甚至没有例外。我想了解,为什么 mojarra 选择将 activeViewMapsSize 设置为高于 numberOfLogicalViews 并且不一样,因为我们希望调整内存消耗而不出现不可预测的行为。

更新 2

我们在 Mojarra 创建了一个问题:JAVASERVERFACES-4015。

【问题讨论】:

我的意思是如果你在上下文中设置 ACTIVE_VIEW_MAPS_SIZE (使用正确的字符串)。你试过了吗? 我在会话映射中设置了它,它起作用了(解决方案 2)。没有像 com.sun.faces.application.view.activeViewMapsSize 这样的 WebContextInitParameter(ACTIVE_VIEW_MAPS_SIZE 后面的值)。我想知道的是值 25!为什么这么多。 这是一个疏忽。很尴尬的一个。应该是numberOfLogicalViews。周围的工作是一个很好的。另一种方法是切换到 OmniFaces @ViewScoped,它确实尊重 numberOfLogicalViews 它在技术上应该与numberOfLogicalViews * numberOfViewsInSession 相同,但numberOfViewsInSession 现在很少有用了。至少,每个视图状态都应该附加一个视图范围 bean,并且当关联的视图状态被删除时,不应该有悬空的视图范围 bean。 回到这个问题,从 OmniFaces 2.2 开始,它的@ViewScoped 将在页面卸载后立即销毁,因此不再不必要地停留在会话中。 【参考方案1】:

为什么 Mojarra 将最近 25 个视图的 ViewScoped Beans 保留在内存中?

因为网页也可以在新的浏览器选项卡中打开,而不是在当前浏览器选项卡中打开。不幸的是,没有可靠的方法可以根据普通的 HTTP GET 请求来确定视图是在现有浏览器选项卡中打开还是在新浏览器选项卡中打开。因此,无论网页是否在同一个浏览器选项卡中打开,所有关联的 bean 都保存在内存中。

无论如何 - 当从一个视图导航到另一个视图时,会话内存大小会越来越大。

如果您在同一个浏览器选项卡中从一个视图导航到另一个视图,这确实没有意义。但是,当您在新的浏览器选项卡中打开下一个视图时,这确实有意义。这样,当您切换回上一个浏览器选项卡并继续与那里的视图交互时,上一个浏览器选项卡中的视图可以正常工作。

我们不能在短期内减小 bean 的大小,所以这种行为会产生相当大的影响。

技术上可以在客户端检测当前页面是否已卸载,并通知服务器这种情况。这些天,pagehide 事件可以用来检查当前视图是否已经在客户端被销毁,navigator.sendBeacon 可以用来以可靠的方式通知服务器这种情况(使用组合例如unloadXMLHttpRequest 不太可靠,因为不能保证它是否真的会准时到达服务器。

自 OmniFaces 2.2(2015 年 11 月)以来,这一切都在 OmniFaces @ViewScoped 背后的逻辑中实现,并且自 OmniFaces 2.7.3(2019 年 11 月)以来经过多年结晶成目前的形式。如果您已经在使用 CDI 来管理 bean,那么应该将源代码中的 import javax.faces.view.ViewScoped; 行替换为 import org.omnifaces.cdi.ViewScoped; 以便利用它。在我参与过的一个项目中,自从原生 JSF 视图范围 bean 迁移到 OmniFaces 视图范围 bean 后,内存使用量减少了 70%。

另见:

com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews How detect and remove (during a session) unused @ViewScoped beans that can't be garbage collected JSF: Mojarra vs. OmniFaces @ViewScoped: @PreDestroy called but bean can't be garbage collected

【讨论】:

以上是关于JSF 2.2 内存消耗:为啥 Mojarra 将最后 25 个视图的 ViewScoped Beans 保留在内存中?的主要内容,如果未能解决你的问题,请参考以下文章

JSF 2.2 ViewScoped Bean 被多次创建

如何将 Web 应用程序从 JSF Mojarra 2.0.5 升级到最新的 Mojara 2.1.9

JSF 2.2 h:inputFile 不适用于漂亮的面孔[重复]

在 JSF 1.2 中使用 Mojarra 配置 JSF 视图状态加密

Mojarra 版本是不是与 JSF 版本对应?

在 Apache Tomcat 7.0.42 及更高版本上部署 Mojarra 2.1.x 和 2.2 会导致 java.lang.UnsupportedOperationException