Утечка памяти с компонентом ViewScoped?

В нашем проекте JavaEE6 (EJB3, JSF2) на JBoss 7.1.1 кажется, что у нас есть утечка памяти с SeamFaces @ViewScoped,

Мы сделали небольшой прототип, чтобы проверить факт:

  • мы используем JMeter для вызова страницы 200 раз;
  • страница содержит и вызывает bean-объект видимости, который внедряет EJB с состоянием;
  • мы фиксируем время ожидания сеанса на 1 минуту.

В конце теста мы проверяем содержимое памяти с помощью VisualVM, и вот что мы получили:

  • с @ViewScoped боб, мы все еще получаем 200 экземпляров MyController - и @PreDestroy метод никогда не вызывается;
  • с @ConversationScoped фасоль, @preDestroy метод называется конец сеанса, и тогда мы получили чистую память.

Мы плохо используем область просмотра или это действительно ошибка?


Вот страница XHTML:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:f="http://java.sun.com/jsf/core"
   xmlns:h="http://java.sun.com/jsf/html"
   xmlns:ui="http://java.sun.com/jsf/facelets"   
   xmlns:s="http://jboss.org/seam/faces">
   <f:metadata>
        <f:viewParam name="u" value="#{myBean.uselessParam}" />
        <s:viewAction action="#{myBean.callService}" />
   </f:metadata>
   <h:body >
        <f:view>
        </f:view>
   </h:body>    
</html>

Теперь включенный боб myBean, Для @ConversationScoped вариант, все комментируемые части не комментируются.

@ViewScoped
// @ConversationScoped
@Named
public class MyBean implements Serializable 
{
    @Inject
    MyController myController;
    //@Inject
    //Conversation conversation;

    private String uselessParam;

    public void callService()
    {
        //if(conversation.isTransient())
        //{
        //            conversation.begin();
        //}
        myController.call();
    }

    public String getUselessParam() 
    {
        return uselessParam;
    }

    public void setUselessParam(String uselessParam) 
    {
        this.uselessParam = uselessParam;
    }
}

А затем вводится боб с состоянием MyController:

@Stateful
@LocalBean
public class MyController
{
   public void call()
   {
         System.out.println("call ");
   }

   @PreDestroy
   public void destroy()
   {
         System.out.println("Destroy");
   }
}

3 ответа

Решение

Я вижу, что многие разработчики довольны @ViewAccessScoped в Myface CODI. Не могли бы вы попробовать и рассказать об этом.

Я столкнулся с вышеупомянутой проблемой в управляемом JSF bean-компоненте @ViewScoped. Обратившись к нескольким блогам, я понял, что JSF сохраняет состояния bean-компонентов вида в сеансе http и уничтожается только при недействительности сеанса. Всякий раз, когда мы нажимаем на страницу jsf каждый раз, когда создается новый компонент области видимости, указанный на странице. Я работал вокруг с использованием Spring Custom View Scope. Работает нормально. Ниже приведен подробный код.

Для JSF 2.1:

Шаг 1. Создайте прослушиватель постконструктивного объекта View Scope Bean следующим образом.

    public class ViewScopeBeanConstructListener implements ViewMapListener {

     @SuppressWarnings("unchecked")
     @Override
     public void processEvent(SystemEvent event) throws AbortProcessingException {
        if (event instanceof PostConstructViewMapEvent) {
            PostConstructViewMapEvent viewMapEvent = (PostConstructViewMapEvent) event;
            UIViewRoot viewRoot = (UIViewRoot) viewMapEvent.getComponent();
            List<Map<String, Object>> activeViews = (List<Map<String, Object>>) 
                 FacesContext.getCurrentInstance().getExternalContext().getSessionMap(). get("com.org.jsf.activeViewMaps");
            if (activeViews == null) {
                activeViews = new ArrayList<Map<String, Object>>();
                activeViews.add(viewRoot.getViewMap());
                FacesContext.getCurrentInstance().getExternalContext().getSessionMap(). put("com.org.jsf.activeViewMaps", activeViews);
            } else {
                activeViews.add(viewRoot.getViewMap());
            }
        }
    }

Шаг 2: Зарегистрируйте прослушиватель событий в face-config.xml

<system-event-listener>
    <system-event-listener-class>
         com.org.framework.custom.scope.ViewScopeBeanConstructListener
    </system-event-listener-class>
   <system-event-class>javax.faces.event.PostConstructViewMapEvent</system-event-class>
    <source-class>javax.faces.component.UIViewRoot</source-class>
</system-event-listener>

Шаг 3. Создайте bean-компонент Custom View Scope следующим образом.

 public class ViewScope implements Scope {

    @Override
    public Object get(String name, ObjectFactory objectFactory) {
        Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
            if (viewMap.containsKey(name)) {
                    return viewMap.get(name);
            } else {
                 List<Map<String, Object>> activeViewMaps = (List<Map<String, Object>>)
                 FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("com.org.jsf.activeViewMaps");
                 if (activeViewMaps != null && !activeViewMaps.isEmpty() 
                     && activeViewMaps.size() > 1) {
                        Iterator iterator = activeViewMaps.iterator();
                     if (iterator.hasNext()) {
                             Map<String, Object> oldViewMap = (Map<String, Object>)
                             iterator.next();
                             oldViewMap.clear();
                             iterator.remove();
                     }
                  }
                Object object = objectFactory.getObject();
                viewMap.put(name, object);
                return object;
           }

    }

Примечание. Другие переопределенные методы могут быть пустыми.

Для JSF 2.2:

JSF 2.2 сохраняет в качестве ключа карты навигации с навигацией в сеансе http в файле com.Sun.faces.application.view.activeViewMaps. Поэтому добавьте приведенный ниже код в Spring Custom View Scope. Нет необходимости в слушателях, как в JSF 2.1

public class ViewScope implements Scope {

    public Object get(String name, ObjectFactory objectFactory) {
         Map<String, Object> viewMap =  
           FacesContext.getCurrentInstance().getViewRoot().getViewMap();
              if (viewMap.containsKey(name)) {
                     return viewMap.get(name);
              } else {
                       LRUMap lruMap = (LRUMap) FacesContext.getCurrentInstance().
     getExternalContext().getSessionMap().get("com.sun.faces.application.view.activeViewMaps");
                if (lruMap != null && !lruMap.isEmpty() && lruMap.size() > 1) {
                   Iterator itr = lruMap.entrySet().iterator();
                   while (itr.hasNext()) {//Not req
                     Entry entry = (Entry) itr.next();
                     Map<String, Object> map = (Map<String, Object>) entry.getValue();
                     map.clear();
                     itr.remove();
                     break;
                   }
                }
                Object object = objectFactory.getObject();
                viewMap.put(name, object);
                return object;
        }
 }

Скорее всего, это ошибка. Честно говоря, реализация Seam 3 была не так уж хороша, а реализация CODI (а также то, что будет в DeltaSpike) намного лучше.

Другие вопросы по тегам