JSF 2.2. Потребление памяти: почему Мохарра хранит в памяти объекты ViewScoped последних 25 видов?
Память за сеанс растет
Мы испытываем высокое потребление памяти при использовании JSF 2.2 (2.2.12) с Mojarra. После исследования наших нагрузочных тестов выяснилось, что размер данных в наших компонентах ViewScoped Beans довольно велик (иногда более 1 МБ). В любом случае - при переходе от представления к представлению объем памяти сеанса увеличивается и увеличивается. Мы не можем уменьшить размер 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. Видя это, мы реализовали Session Listener, который устанавливает это значение равным 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 представлений в памяти, если не определено другое значение в сеансе. Я не могу найти документацию по этому поводу. Может кто-нибудь объяснить, для чего это нужно? Это для браузера назад? На наших страницах JSF отключено кэширование. Так что браузер обратно всегда будет создавать новый вид. Это не должно быть проблемой для нас.
Решение 2 - верный подход? Может ли кто-нибудь объяснить недостатки этого подхода?
Обновление 1
После различных комментариев и более глубокой отладки выяснилось, что:
com.sun.faces.numberOfLogicalViews
определяет размер logicViewMap, в котором хранится только (!) состояние дерева компонентов пользовательского интерфейсаcom.sun.faces.application.view.activeViewMapsSize
определяет размер activeViewMap, который содержит bean-компоненты ViewScoped
При смене numberOfLogicalViews
до 1, mojarra будет по-прежнему отслеживать все компоненты видимости из последних 25 представлений. Когда вы настраиваете наоборот - numberOfLogicalViews
до 15 и activeViewMapsSize
1 - представление не может быть правильно инициализировано из-за отсутствия данных, я думаю. Мы даже не получили исключения. Я хотел бы понять, почему Мохарра решил установить activeViewMapsSize
выше чем numberOfLogicalViews
и не то же самое, так как мы хотим настроить потребление памяти без непредсказуемого поведения.
Обновление 2
Мы создали проблему в Mojarra JAVASERVERFACES-4015
1 ответ
Почему Mojarra хранит в памяти компоненты ViewScoped последних 25 представлений?
Потому что веб-страницу также можно открыть в новой вкладке браузера, а не в текущей вкладке браузера. К сожалению, не существует надежного способа определить на основе простого ванильного HTTP-запроса GET, открывается ли представление в существующей вкладке браузера или в новой вкладке браузера. Следовательно, все связанные bean-компоненты хранятся в памяти независимо от того, открыта ли веб-страница в той же вкладке браузера или нет.
Так или иначе - при переходе от представления к представлению размер памяти сеанса все растет и растет.
Это действительно не имеет смысла, если вы переходите от представления к представлению в одной и той же вкладке браузера. Но это имеет смысл, когда вы открываете следующее представление в новой вкладке браузера. Таким образом, представление в предыдущей вкладке браузера продолжает нормально работать, когда вы переключаетесь обратно на предыдущую вкладку браузера и продолжаете взаимодействовать с представлением там.
Мы не можем уменьшить размер bean-компонентов на короткое время, поэтому такое поведение оказывает определенное влияние.
Технически возможно определить на стороне клиента, была ли выгружена текущая страница, и уведомить об этом сервер. В эти дниpagehide
событие можно использовать для проверки того, было ли уничтожено текущее представление на стороне клиента, иnavigator.sendBeacon
может использоваться для надежного уведомления сервера об этом условии (используя комбинацию, например,
unload
а также
XMLHttpRequest
менее надежен, так как нет гарантии, что он попадет на сервер вовремя).
Все это реализовано в логике OmniFaces.
@ViewScoped
начиная с OmniFaces 2.2 (ноябрь 2015 г.) и с годами кристаллизовался в свою нынешнюю форму, начиная с OmniFaces 2.7.3 (ноябрь 2019 г.). Если вы уже используете CDI для управления bean-компонентами, вам следует заменить
import javax.faces.view.ViewScoped;
строк в исходном коде
import org.omnifaces.cdi.ViewScoped;
для того, чтобы использовать это. В одном проекте, с которым я работал, использование памяти уменьшилось на 70 % с момента переноса собственных компонентов JSF с областью видимости на компоненты OmniFaces с областью видимости.
Смотрите также:
- com.sun.faces.numberOfViewsInSession против com.sun.faces.numberOfLogicalViews
- Как обнаружить и удалить (во время сеанса) неиспользуемые bean-компоненты @ViewScoped, которые не могут быть удалены сборщиком мусора
- JSF: Mojarra против OmniFaces @ViewScoped: @PreDestroy вызывается, но бин не может быть собран мусором