JSF 2.2 内存消耗:为什么 Mojarra 在内存中保留最后 25 个视图的 ViewScoped Bean?

JSF 2.2 Memory Consumption: Why does Mojarra keep the ViewScoped Beans of the last 25 Views in Memory?

每个会话的内存增长

我们在使用带有 Mojarra 的 JSF 2.2 (2.2.12) 时遇到高内存消耗。在调查了我们的负载测试之后,我们发现 ViewScoped Beans 中的数据大小是 quite 高(有时超过 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

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

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

更新 2

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

Why does Mojarra keep the ViewScoped Beans of the last 25 Views in Memory?

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

Anyway - when navigating from view to view, the session memory size grows and grows.

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

We can't decrease the size of the beans on short-term, so this behavior has quite some impact.

技术上可以在客户端检测当前页面是否已卸载,并将此情况通知服务器。如今,pagehide event can be used to check whether the current view has been destroyed in the client side, and the 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