2015-05-21 7 views
5

Ich habe eine Frage in Bezug auf den Lebenszyklus von CDI Bohnen Session-Bereich.
Soweit ich verstehe, wird eine CDI-Session-Session-CD vom Container erstellt, wenn die Sitzung beginnt, und beim Beenden der Sitzung gelöscht. Bevor die Bean zerstört wird, wird die @ PreDestroy Methode wie hier beschrieben aufgerufen https://docs.oracle.com/javaee/6/tutorial/doc/gmgkd.html. Es heißt auch, Ressourcen in dieser Methode freizugeben.
CDI-Sitzung Bereich beanspruchte Bohne nicht zerstört führt zu Speicherverlust

In einer JSF-Anwendung, die ich baue ich Speicherverlust erfahren, weil die Bohne scheint nicht zerstört zu werden und damit die @PreDestroy Methode wird aufgerufen, nicht einige Hinweise für den Garbage Collector zu befreien. Also habe ich eine einfache Anwendung erstellt, um das Verhalten zu testen. Meine Erfahrung ist, dass die Session-Bean nicht zerstört wird, wenn die Sitzung vorbei ist und außerdem wird sie nicht zerstört, wenn der Speicherplatz benötigt wird. Ich kann nicht glauben, ich bin die erste Begegnung, aber ich habe keine Informationen zu diesem Verhalten findet ..

Also meine Frage ist: Sollte keine CDI Bean zerstört werden - und damit die @PreDestroy Methode aufgerufen werden - unmittelbar nachdem der Kontext abgelaufen ist? Und wenn nicht sollte es nicht zumindest zerstört werden, wenn der Platz benötigt wird?

Mein Test Anwendung:

Ich bin nicht ein Bild, um es erlaubt, aber der Umriss ist die sehr einfache JSF Webapp von Eclipse erzeugt. Ich habe auch die Datei 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> 

ich die index.xhtml Seite öffnen und die @PostConstruct Die Methode wird wie erwartet aufgerufen. Der Heap-Speicherbereich wird überschritten, wenn ich daten_1 und daten_2 beide aufruft, ohne dazwischen zu arbeiten. Wenn ich eine der Ressourcen dazwischen freigebe oder eine Methode zweimal hintereinander aufrufe, reicht der Heapspeicher aus, da der Garbage Collector den Speicher freigibt. Das funktioniert so, wie ich es erwarten würde.

Aber wenn ich eine Datenfunktion aufrufen, schließen Sie den Browser und damit die Sitzung, öffnen Sie einen neuen Browser und rufen Sie eine der Datenfunktionen erneut auf, dann funktioniert die Anwendung als (ich denke) der Speicherplatz überschritten wird . Der Punkt ist: Die erste Session-Bean wird nicht zerstört und ihre @ PreDestroy Methode wird nicht aufgerufen und daher befindet sich die ArrayList noch im Speicher.

Kann mir bitte jemand erklären, was hier vor sich geht? Sollte eine CDI-Bean nicht durch den Container zerstört werden, sobald der Kontext abgelaufen ist, so dass Verweise auf null gesetzt werden können und der Garbage Collector Ressourcen freigeben kann?
Ich verwende JBoss AS 7.1.1 und seine Standardimplementierung JSF Mojarra 2.1.

+0

JBoss AS 7.1.1 ist uralt. Versuche zumindest die aktuelle Weld-Version, um einen bekannten und bereits lange behobenen Fehler auszuschließen. – BalusC

+0

Ok, danke BalusC, ich werde es versuchen und wiederkommen! –

+0

Ich habe die WELD-Implementierung auf 1.1.23 aktualisiert, aber es hat nicht geholfen. –

Antwort

2

Die Antwort von @olexd erklärt im Grunde, was ich falsch verstanden habe, vielen Dank! Es ist jedoch keine Option, die Sitzung nach einer bestimmten Zeit für ungültig zu erklären, also musste ich auch den Kommentar von @ geert3 verwenden, danke dafür! Ich beantworte meine eigene Frage, um zu zeigen, wie ich mein spezielles Problem hier im Detail gelöst habe.

Worüber ich mich irrte: Ich dachte, dass die Sitzung abläuft, sobald der Browser geschlossen ist. Das ist falsch und macht Sinn. Möglicherweise möchten Sie den Browser schließen und erneut öffnen, um in derselben Sitzung wie zuvor zu arbeiten.
Für mich ist dieses Verhalten nicht geeignet, da ich Ressourcen freigeben möchte, sobald der Browser geschlossen wird. So ist die Antwort auf manuell die Sitzung wie folgt zu entkräften:

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

Sobald diese Methode aufgerufen wird, die @PreDestroy Methode genannt wird, genau so, wie ich es will. Jetzt musste ich feststellen, wann diese Funktion aufgerufen werden sollte. Ich suchte nach einem Weg, um etwas wie eine browserclose Ereignis zu hören. Es gibt die onbeforeunload und onunload Ereignisse. onunload scheint nicht für mich in Chrome zu arbeiten, aber die onbeforeunload tut. Siehe auch diese Antwort: https://stackoverflow.com/a/16677225/1566562

Also schrieb ich einen versteckten Knopf, der eine entsprechende backingbean Methode von Javascript beforeunload und ruft angeklickt wird. Das funktioniert so, wie ich es erwarten würde. Ich habe es auf Chrome 43.0.2357.65 und IE 11 getestet, jetzt bin ich damit zufrieden. Allerdings funktioniert es nicht mit onunload, aber das ist nicht von Interesse für mich jetzt.

Also meine letzte Code gefällt das:

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

} 

Bitte beachte, dass ich JQuery verwendet haben. Dies funktioniert mit JBoss AS 7.1.1 und der Standard-Weld-Implementierung.
Eine Sache hinzuzufügen: Man muss nicht alle Referenzen manuell auf null setzen. Dies macht auch Sinn, da es mühsam wäre.

6

Session-Beans (unabhängig von CDI oder JSF verwaltet) bleiben aktiv, bis ein Sitzungszeitlimit überschritten wird (in der Regel 30 Minuten, abhängig vom Anwendungsserver), die Sie in web.xml angeben können. Wenn Sie den Browser schließen, wird die Sitzung nicht ungültig und sie wartet darauf, vom Servlet-Container nach Ablauf des Zeitlimits gelöscht zu werden.Also, meine Annahme, ein solches Verhalten ist in Ordnung, @PreDestroy Methode wird später aufgerufen.

+0

Danke olexd, du hast Recht. Ich stelle folgendes in mein Web ein.xml: ' 1 ' und nach einer Minute wurde die Bean zerstört und die _ @ PreDestroy_ Methode aufgerufen –

Verwandte Themen