2015-05-21 2 views
5

У меня есть вопрос относительно жизненного цикла бинов CDI с сессией.
Насколько я понимаю, бит CDI, обработанный сеансом, сконфигурирован контейнером, когда сеанс начинается и уничтожается при завершении сеанса. Перед уничтожением bean-компонента @PreDestroy Метод вызывается, как описано здесь https://docs.oracle.com/javaee/6/tutorial/doc/gmgkd.html. В нем также говорится о выпуске ресурсов в этом методе.
Сессия, обработанная сеансом связи CDI, не разрушена, что приводит к утечке памяти

В приложении JSF я строй я переживаю утечку памяти, так как фасоль, кажется, не будет разрушена, и, следовательно, @PreDestroy метод не вызывается, чтобы освободить некоторые ссылки для сборщика мусора. Поэтому я создал простое приложение для тестирования поведения. Мой опыт в том, что сеансовый компонент не разрушается, когда сеанс завершен, и, кроме того, он даже не уничтожается, когда требуется пространство памяти. Я не могу поверить, что я первый столкнуться с этим, но я не нахожу никакой информации об этом поведении ..

Так что мой вопрос: не должен ли CDI боб быть разрушен - и, следовательно, @PreDestroy Вызывается метод - сразу после истечения срока его использования? И если это не должно быть, по крайней мере, уничтожено, когда пространство необходимо?

Мой тест Применение:

Я не разрешается размещать изображения, но план является очень простой JSF WebApp генерируется затмение. У меня также есть файл beans.xml.

Test.java:

package com.test; 

import java.io.Serializable; 
import java.util.ArrayList; 

import javax.annotation.PostConstruct; 
import javax.annotation.PreDestroy; 
import javax.enterprise.context.SessionScoped; 
import javax.inject.Named; 

@SessionScoped 
@Named 
public class Test implements Serializable { 

    /** 
    * 
    */ 
    private static final long serialVersionUID = 1L; 
    private String test; 
    private ArrayList<ComplexType> cps; 
    private ArrayList<ComplexType> cps_2; 

    @PostConstruct 
    public void init() { 
     System.out.println("test postconstruct.."); 
     test = "Cdi Test"; 
    } 

    @PreDestroy 
    public void cleanUp() { 
     cps = null; 
     cps_2 = null; 
     System.out.println("test cleanUp...."); 
    } 

    public void data_1() { 

     cps = new ArrayList<ComplexType>(); 

     for(int i = 0; i < 800; i++) { 
      String[] s = new String[100000]; 
      ComplexType cp = new ComplexType(i, s); 
      cps.add(cp); 
      System.out.println(i); 
     } 
     System.out.println("data_1"); 
    } 

    public void free_1() { 
     cps = null; 
     System.out.println("free_1"); 
    } 

    public void data_2() { 

     cps_2 = new ArrayList<ComplexType>(); 

     for(int i = 0; i < 800; i++) { 
      String[] s = new String[100000]; 
      ComplexType cp = new ComplexType(i, s); 
      cps_2.add(cp); 
      System.out.println(i); 
     } 
     System.out.println("data_1"); 
    } 

    public void free_2() { 
     cps_2 = null; 
     System.out.println("free_1"); 
    } 

    public String getTest() { 
     return test; 
    } 

    public void setTest(String test) { 
     this.test = test; 
    } 
} 

ComplexType.java:

package com.test; 

public class ComplexType { 

    private int id; 
    private String[] name; 

    public ComplexType(int id, String[] name) { 

     this.id = id; 
     this.name = name; 
    } 
    public int getId() { 
     return id; 
    } 
    public void setId(int id) { 
     this.id = id; 
    } 
    public String[] getName() { 
     return name; 
    } 
    public void setName(String[] name) { 
     this.name = name; 
    } 
} 

index.xhtml:

<html xmlns="http://www.w3.org/1999/xhtml" 
xmlns:h="http://java.sun.com/jsf/html" 
xmlns:f="http://java.sun.com/jsf/core" 
> 

<h:head> 
    <title>Cdi test </title> 
</h:head> 

<h:body> 

    <h:outputText value="#{test.test}"></h:outputText> 

    <h:form> 
     <h:commandButton value="cp_1 data" actionListener="#{test.data_1}"> 
      <f:ajax></f:ajax> 
     </h:commandButton> 
     <h:commandButton value="cp_1 Free" actionListener="#{test.free_1}"> 
      <f:ajax></f:ajax> 
     </h:commandButton> 

     <br></br> 
     <h:commandButton value="cp_2 data" actionListener="#{test.data_2}"> 
      <f:ajax></f:ajax> 
     </h:commandButton> 
     <h:commandButton value="cp_2 Free" actionListener="#{test.free_2}"> 
      <f:ajax></f:ajax> 
     </h:commandButton> 
    </h:form> 

</h:body> 
</html> 

открыть страницу index.xhtml и @PostConstruct Метод вызывается, как ожидалось. Пространство кучи превышено, когда я вызываю data_1 и data_2 без освобождения между ними. Когда я освобождаю один из ресурсов между ними или я вызываю один метод дважды подряд, достаточно места кучи, поскольку сборщик мусора освобождает память. Это работает так, как я ожидаю, что он сработает.

Но когда я вызываю одну функцию данных, закройте браузер и, следовательно, сеанс, откройте новый браузер и снова вызовите одну из функций данных, тогда приложение перестает работать, поскольку (я думаю), объем памяти превышен , Дело в том, что первый сеансовый компонент не уничтожается и его @PreDestroy Метод не вызывается, и поэтому ArrayList все еще находится в памяти.

Может кто-нибудь, пожалуйста, объясните мне, что здесь происходит? Не следует ли уничтожить контейнер CDI контейнером, как только его контекст истечет, так что ссылки могут быть установлены равными нулю, а сборщик мусора может освобождать ресурсы?
Я использую JBoss AS 7.1.1 и его реализацию по умолчанию JSF Mojarra 2.1.

+0

JBoss AS 7.1.1 древний. По крайней мере, попробуйте текущую версию Weld, чтобы исключить из-за известной и уже давно исправленной ошибки. – BalusC

+0

Хорошо, спасибо BalusC, я попробую это и вернусь! –

+0

Я обновил реализацию WELD до 1.1.23, но это не помогло. –

ответ

2

Ответ @olexd в основном объясняет, что я ошибался в своем уме, спасибо вам большое! Но недействительность сеанса после определенного периода не является вариантом, поэтому мне пришлось использовать комментарий @ geert3, спасибо за это! Я отвечаю на свой вопрос, чтобы показать, как я подробно решил свою конкретную проблему.

Что я ошибался: я думал, что сеанс истекает, как только браузер закрыт. Это неправильно, и это имеет смысл. Можно закрыть браузер и снова открыть его для работы в том же сеансе, что и раньше.
Для меня это поведение не подходит, потому что я хочу освобождать ресурсы, как только браузер закрывается. Таким образом, ответ должен вручную сессию недействительной, как это:

FacesContext.getCurrentInstance().getExternalContext().invalidateSession(); 

Как только этот метод вызывается, @PreDestroy Метод называется, точно так, как я хочу. Теперь мне нужно было определить, когда нужно вызвать эту функцию. Я искал способ прослушать что-то вроде browserclose. Есть onbeforeunload и onunload событий. onunload, похоже, не работает для меня в Chrome, но onbeforeunload. Смотрите также ответ: https://stackoverflow.com/a/16677225/1566562

Так что я написал скрытую кнопку, которая получает щелкнул по JavaScript на beforeunload и вызывает соответствующий метод backingbean. Это работает так, как я ожидаю, что он сработает. Я тестировал его на Chrome 43.0.2357.65 и IE 11, пока я доволен им. Однако он не работает с onunload, но сейчас это меня не беспокоит.

Так что мой окончательный код любит это:

index.xhtml

<html xmlns="http://www.w3.org/1999/xhtml" 
    xmlns:h="http://java.sun.com/jsf/html" 
    xmlns:f="http://java.sun.com/jsf/core"> 

<h:head> 
    <title>Cdi test</title> 
    <h:outputScript library="default" name="js/jquery-1.11.3.min.js" 
     target="head"></h:outputScript> 
</h:head> 

<h:body> 

    <h:outputText value="#{test.test}"></h:outputText> 

    <h:form id="overall"> 
     <h:commandButton value="cp_1 data" actionListener="#{test.data_1}"> 
      <f:ajax></f:ajax> 
     </h:commandButton> 
     <h:commandButton value="cp_1 Free" actionListener="#{test.free_1}"> 
      <f:ajax></f:ajax> 
     </h:commandButton> 

     <br></br> 
     <h:commandButton value="cp_2 data" actionListener="#{test.data_2}"> 
      <f:ajax></f:ajax> 
     </h:commandButton> 
     <h:commandButton value="cp_2 Free" actionListener="#{test.free_2}"> 
      <f:ajax></f:ajax> 
     </h:commandButton> 

     <br></br> 

     <h:commandButton id="b" style="display:none" 
      actionListener="#{test.invalidate}"></h:commandButton> 

    </h:form> 

    <script type="text/javascript"> 
     $(window).on('beforeunload', function() { 
      $('#overall\\:b').click(); 
     }); 
    </script> 
</h:body> 
</html> 

Test.java

package com.test; 

import java.io.Serializable; 
import java.util.ArrayList; 

import javax.annotation.PostConstruct; 
import javax.annotation.PreDestroy; 
import javax.enterprise.context.SessionScoped; 
import javax.faces.context.FacesContext; 
import javax.inject.Named; 

@SessionScoped 
@Named 
public class Test implements Serializable { 

    /** 
    * 
    */ 
    private static final long serialVersionUID = 1L; 
    private String test; 
    private ArrayList<ComplexType> cps; 
    private ArrayList<ComplexType> cps_2; 

    @PostConstruct 
    public void init() { 
     System.out.println("test postconstruct.."); 
     test = "Cdi Test"; 
    } 

    @PreDestroy 
    public void cleanUp() { 
     cps = null; 
     cps_2 = null; 
     System.out.println("test cleanUp...."); 
    } 

    public void data_1() { 

     cps = new ArrayList<ComplexType>(); 

     for (int i = 0; i < 800; i++) { 
      String[] s = new String[100000]; 
      ComplexType cp = new ComplexType(i, s); 
      cps.add(cp); 
      System.out.println(i); 
     } 
     System.out.println("data_1"); 
    } 

    public void free_1() { 
     cps = null; 
     System.out.println("free_1"); 
    } 

    public void data_2() { 

     cps_2 = new ArrayList<ComplexType>(); 

     for (int i = 0; i < 800; i++) { 
      String[] s = new String[100000]; 
      ComplexType cp = new ComplexType(i, s); 
      cps_2.add(cp); 
      System.out.println(i); 
     } 
     System.out.println("data_2"); 
    } 

    public void free_2() { 
     cps_2 = null; 
     System.out.println("free_2"); 
    } 

    public void invalidate() { 
     FacesContext.getCurrentInstance().getExternalContext().invalidateSession(); 
     System.out.println("invalidate"); 
    } 

    public String getTest() { 
     return test; 
    } 

    public void setTest(String test) { 
     this.test = test; 
    } 

} 

Обратите внимание, что я использовал JQuery. Это работает с JBoss AS 7.1.1 и реализацией по умолчанию Weld.
Одна вещь, которую нужно добавить: не нужно устанавливать все референции вручную до нуля. Это имеет смысл, так как это было бы утомительно ..

6

Сессионные компоненты (независимо от управляемых CDI или JSF) остаются в живых до тех пор, пока не превысит определенный интервал сеанса (обычно 30 минут по умолчанию, в зависимости от сервера приложений), который вы можете указать в web.xml. Простое закрытие браузера не отменяет сеанс, и он ждет, чтобы его уничтожили контейнером сервлетов после истечения срока ожидания.Итак, мое предположение, такое поведение просто отлично, метод @PreDestroy будет вызываться позже.

+0

Спасибо, олльд, ты прав. Я установил следующее в своей сети.xml: ' 1 ' , и через одну минуту бит был уничтожен и был вызван метод _ @ PreDestroy_ –