2015-05-21 19 views
5

Ho una domanda riguardante il ciclo di vita dei bean CDI con scope di sessione.
Per quanto ho capito, un bean CDI con scope di sessione viene costruito dal contenitore quando la sessione viene avviata e distrutta al termine della sessione. Prima che il bean venga distrutto, il metodo @PreDestroy viene richiamato come descritto qui https://docs.oracle.com/javaee/6/tutorial/doc/gmgkd.html. Dice anche di rilasciare risorse in questo metodo.
Il bean con scope sessione CDI non distrutto provoca perdite di memoria

In un'applicazione JSF costruisco io sperimento Perdita di memoria, perché il fagiolo non sembra essere distrutti e quindi il @PreDestroy metodo non viene richiamato per liberare alcuni riferimenti per il garbage collector. Così ho creato una semplice applicazione per testare il comportamento. La mia esperienza è che il bean di sessione non viene distrutto quando la sessione è finita e inoltre non viene nemmeno distrutto quando è necessario lo spazio di memoria. Non posso credere che io sono il primo a incontrare questo, ma non trovo alcuna informazione su questo comportamento ..

Quindi la mia domanda è: Non deve essere distrutto un fagiolo CDI - e quindi il @PreDestroy Il metodo deve essere invocato immediatamente dopo la scadenza del suo contesto? E se no, non dovrebbe essere almeno distrutto quando lo spazio è necessario?

Il mio test di applicazione:

io non sono autorizzato a postare una foto, ma il contorno è la webapp JSF molto di base generata da Eclipse. Ho anche il file 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> 

apro la pagina di index.xhtml e la @PostConstruct Il metodo viene richiamato come previsto. Lo spazio heap viene superato quando invoco data_1 e data_2 entrambi senza liberare in mezzo. Quando svuoto una delle risorse in mezzo o invoco un metodo due volte di seguito, lo spazio heap è sufficiente, poiché il garbage collector libera la memoria. Funziona come mi aspetterei che funzioni.

Ma quando invoco una funzione di dati, chiudere il browser e quindi la sessione, aprire un nuovo browser e richiamare una delle funzioni di dati ancora una volta, l'applicazione smette di funzionare come (credo) lo spazio di memoria viene superata . Il punto è: il primo bean di sessione non viene distrutto e il suo metodo @PreDestroy non viene richiamato e quindi ArrayList è ancora in memoria.

Qualcuno può spiegarmi che cosa sta succedendo qui? Non dovrebbe un bean CDI essere distrutto dal contenitore non appena il suo contesto scade in modo che i riferimenti possano essere impostati su null e il garbage collector possa liberare risorse?
Sto usando JBoss AS 7.1.1 e la sua implementazione predefinita JSF Mojarra 2.1.

+0

JBoss AS 7.1.1 è antico. Almeno prova la versione corrente di Weld per escludere un bug noto e già lungo fisso dalla causa. – BalusC

+0

Ok, grazie BalusC, ci proverò e tornerò! –

+0

Ho aggiornato l'implementazione di WELD alla versione 1.1.23, ma non è stato di aiuto. –

risposta

2

La risposta di @olexd spiega in sostanza cosa stavo sbagliando nella mia mente, grazie mille! Ma invalidare la sessione dopo un determinato periodo non è un'opzione, quindi ho dovuto usare anche il commento di @ geert3, grazie per questo! Sto rispondendo alla mia domanda per mostrare come ho risolto il mio particolare problema in dettaglio qui.

Cosa ho sbagliato: ho pensato che la sessione scadrà non appena il browser sarà chiuso. Questo è sbagliato e ha senso. Si potrebbe voler chiudere il browser e aprirlo di nuovo per lavorare nella stessa sessione di prima.
Per me questo comportamento non è appropriato perché voglio rilasciare le risorse non appena il browser viene chiuso. Quindi la risposta è di invalidare manualmente la sessione in questo modo:

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

Non appena viene chiamato questo metodo, il @PreDestroy metodo viene chiamato, esattamente come lo voglio. Ora dovevo determinare quando chiamare questa funzione. Ho cercato un modo per ascoltare qualcosa come un evento browser. Ci sono onbeforeunload e onunload eventi. onunload non sembra funzionare per me in Chrome, ma lo onbeforeunload fa. Vedi anche questa risposta: https://stackoverflow.com/a/16677225/1566562

così ho scritto un pulsante nascosto che viene cliccato da javascript beforeunload e invoca un metodo backingbean appropriato. Funziona come mi aspetterei che funzioni. L'ho provato su Chrome 43.0.2357.65 e IE 11, per ora sono contento. Tuttavia non funziona con onunload, ma questo non mi interessa al momento.

Quindi il mio codice finale piace questo:

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; 
    } 

} 

Nota che ho usato JQuery. Questo funziona con JBoss AS 7.1.1 e l'implementazione di Weld di default.
Una cosa da aggiungere: non è necessario impostare manualmente tutti i riferimenti su null. Ciò ha anche senso, come sarebbe noioso ..

6

I bean di sessione (indipendentemente da CDI o JSF gestiti) rimangono attivi fino a quando non supera il timeout di sessione (solitamente 30 minuti per impostazione predefinita, dipendenti dal server delle applicazioni), che è possibile specificare in web.xml. La chiusura del browser non invalida la sessione e attende che venga distrutta dal contenitore servlet dopo la scadenza del timeout.Quindi, suppongo, un comportamento del genere va bene, il metodo @PreDestroy verrà richiamato in seguito.

+0

Grazie olexd, hai ragione. Ho impostato il seguente nel mio web.xml: ' 1 ' e dopo un minuto il bean è stato eliminato e il metodo _ @ PreDestroy_ è stato richiamato –

Problemi correlati