CDI bean session is not destroyed, resulting in a memory leak

I have a question regarding the CDI beans life cycle for a session.
As I understand it, a session-processed CDI bean is created by the container when the session starts and is destroyed when the session ends. Before the bean is destroyed, the @PreDestroy method is called as described here https://docs.oracle.com/javaee/6/tutorial/doc/gmgkd.html . He also says to free up resources in this method.

In the JSF application I built, I experience a memory leak because the bean does not seem to be destroyed, and therefore the @PreDestroy method is not called to free some links to the garbage collector. Therefore, I created a simple behavior testing application. My experience is that a bean session is not destroyed when the session is completed, and in addition, it is not even destroyed when memory space is required. I cannot believe that I am the first to come across this, but I have not found any information about this behavior.

So my question is: shouldn't the CDI bean be destroyed - and therefore the @PreDestroy method is called - right after its expiration? And if it should not be at least destroyed when space is needed?

My test application:

I am not allowed to post a picture, but the outline is the easiest jsf webapp created by eclipse. I also have a beans.xml file.

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> 

I open the index.xhtml page and the @PostConstruct method is called as expected. Heap space is exceeded when I call data_1 and data_2 without freeing in between. When I free one of the resources between them or I call one method twice in a row, there is enough heap space because the garbage collector frees memory. This works the way I expect it to work.

But when I call one data function, close the browser and, therefore, the session, open a new browser and call one of the data functions again, then the application stops working (I think), the memory space is exceeded . The fact is that the first bean session is not destroyed, and its @PreDestroy method is not called, and therefore the ArrayList is still in memory.

Can someone explain to me what is going on here? Should the CDI bean be destroyed by the container as soon as its context expires so that the links can be set to zero and the garbage collector can free up resources?
I am using JBoss AS 7.1.1 and its default implementation is JSF Mojarra 2.1.

+5
source share
2 answers

@Olexd's answer basically explains that I was wrong in my mind, thanks a lot! But invalidating a session after a certain period is not an option, so I had to use @ geert3's comment, thanks for that! I answer my question to show how I solved my specific problem in detail.

What I was wrong with: I thought the session would expire as soon as the browser was closed. This is wrong and it makes sense. You can close the browser and reopen it to work in the same session as before.
This behavior is not suitable for me, because I want to free resources as soon as the browser closes. Therefore, the answer is to manually terminate the session as follows:

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

As soon as this method is called, the @PreDestroy method is called, as I am. Now I needed to determine when to call this function. I was looking for a way to listen for something like a browser event. There are onbeforeunload and onunload events. onunload does not work for me in Chrome, but onbeforeunload does. See also this answer: fooobar.com/questions/408511 / ...

So, I wrote a hidden button that javascript clicks on beforeunload and calls the corresponding backingbean method. This works the way I expect it to work. I tested it on Chrome 43.0.2357.65 and IE 11, while I'm happy with it. However, it does not work with onunload, but now it does not bother me.

So my final code likes this:

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

Please note that I used jQuery. This works with JBoss AS 7.1.1 and the default implementation of Weld. One thing to add: there is no need to manually set all links to zero. That makes sense, as it would be tiring ..

+2
source

The beans session (regardless of whether it is managed by CDI or JSF) remains alive until it exceeds a certain session timeout (usually 30 minutes by default, depending on the application server), which you can specify in web.xml. Simply closing the browser does not cancel the session, and it waits for it to be destroyed by the servlet container after the timeout. So, my guess is that this behavior is just fine, the @PreDestroy method will be called later.

+6
source

All Articles