JSF 2.2的内存消耗:为什么Mojarra会将最近25个视图的ViewScoped Beans保存在内存中?

30

每个会话的内存增长

我们使用JSF 2.2(2.2.12)与Mojarra时遇到了高内存消耗问题。经过对我们的负载测试进行调查,发现我们的ViewScoped Beans中的数据量相当大(有时超过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);
    }
}

显然这很有效。由于只保留一个视图,内存不再增长。

那么为什么要在内存中保存25个视图?

因此,如果会话中未定义不同的值,则Mojarra会在内存中保留25个视图的历史记录。我找不到任何关于此的文档。有人能解释一下这是用于什么的吗?是用于浏览器返回吗?我们已经禁用了JSF页面上的缓存。因此,浏览器返回将始终创建一个新视图。这对我们来说不应该是问题。

解决方案2是否有效?有人能解释一下此方法的缺点吗?

更新1

经过各种评论和更深入的调试,结果显示:

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

numberOfLogicalViews 更改为 1 时,Mojarra 仍将跟踪最近 25 个视图的所有视图范围 bean。当你以另一种方式配置它-将 numberOfLogicalViews 设置为 15,activeViewMapsSize 设置为 1 时-由于缺少数据,视图无法正确初始化,我猜。我们甚至没有得到异常。我想了解为什么 Mojarra 选择将 activeViewMapsSize 设置为比 numberOfLogicalViews 更高而不是相同的,因为我们想调整内存消耗而不会出现不可预测的行为。

更新2

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


1
我的意思是,如果您在上下文中设置了ACTIVE_VIEW_MAPS_SIZE(使用正确的字符串),那会怎样呢?您试过了吗? - Kukeltje
1
我已将其设置在会话映射中,它运行正常(解决方案2)。没有像com.sun.faces.application.view.activeViewMapsSize(ACTIVE_VIEW_MAPS_SIZE值后面的)这样的WebContextInitParameter。让我想知道的是数值25!为什么这么多。 - fischermatte
7
这是一个疏忽,一个令人尴尬的疏忽。本应该是 numberOfLogicalViews。这个解决方法是很好的。另一个选择是切换到 OmniFaces 的 @ViewScoped,它会遵循 numberOfLogicalViews - BalusC
2
从技术上讲,它应该与 numberOfLogicalViews * numberOfViewsInSession 相同,但是现在 numberOfViewsInSession 很少有用。至少,每个视图状态都应该附有一个视图作用域的 bean,并且当关联的视图状态被移除时不应该有悬空的视图作用域 bean。 - BalusC
1
回到这个问题,自从OmniFaces 2.2以来,它的@ViewScoped将在页面卸载时立即销毁,因此不会再无必要地停留在会话中。 - BalusC
显示剩余11条评论
1个回答

2
为什么Mojarra会将最近25个view scoped bean保存在内存中?
因为一个网页也可以在新的浏览器标签页中打开,而不是当前的浏览器标签页。遗憾的是,没有一种绝对可靠的方法可以根据纯粹的HTTP GET请求确定视图是在现有的浏览器标签页中打开还是在新的浏览器标签页中打开。因此,无论网页是否在同一浏览器标签页中打开,所有相关的bean都会被保存在内存中。
无论如何,在从视图导航到视图时,会话内存大小会不断增长。
如果在同一浏览器标签页中从视图导航到视图,这确实毫无意义。但是,当您在新的浏览器标签页中打开下一个视图时,这是有意义的。这样,当您切换回以前的浏览器标签页并继续与那里的视图交互时,以前的浏览器标签页中的视图仍然可以正常工作。
我们不能在短期内减少bean的大小,因此,这种行为会产生相当大的影响。
技术上讲,可以在客户端检测当前页面是否已经卸载,并通知服务器。现在,pagehide事件可用于检查当前视图是否已在客户端中销毁,navigator.sendBeacon可用于可靠地通知服务器此条件(使用例如unloadXMLHttpRequest的组合不太可靠,因为不能保证它是否会及时到达服务器)。
这一切都是在OmniFaces @ViewScoped背后的逻辑中实现的,自OmniFaces 2.2(2015年11月)以来,在几年的时间里,它已经结晶化成了它目前的形态,自OmniFaces 2.7.3(2019年11月)以来。如果您已经使用CDI来管理bean,那么只需要将您源代码中的import javax.faces.view.ViewScoped;行替换为import org.omnifaces.cdi.ViewScoped;就可以利用它了。在我曾经参与的一个项目中,由于将本机JSF视图作用域bean迁移到OmniFaces视图作用域bean,内存使用量减少了70%。

另请参阅:


嗨!在JSF 2.3中,这个问题解决了吗? - dssof

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接