I have a question regarding the lifecycle of session scoped CDI beans.
As far as I understand, a session scoped CDI bean is constructed by the container when the session starts and destroyed when the session ends. Before the bean is destroyed the @PreDestroy Method is invoked as described here https://docs.oracle.com/javaee/6/tutorial/doc/gmgkd.html. It also says to release resources in this method.
In a JSF application I build I experience Memory Leak because the bean doesn't seem to be destroyed and hence the @PreDestroy Method is not invoked to free some references for the garbage collector. So I built a simple Application to test the behavior. My experience is that the session bean doesn't get destroyed when the session is over and furthermore it doesn't even get destroyed when the memory space is needed. I cannot believe I am the first to encounter this, but I don't find any information about this behavior..
So my question is: Shouldn't a CDI bean be destroyed - and hence the @PreDestroy Method be invoked - immediately after its context expired? And if not shouldn't it be at least destroyed when the space is needed?
My test Application:
I am not allowed to post a picture, but the outline is the very basic jsf webapp generated by eclipse. I also have the 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 gets invoked as expected. The heap space is exceeded when I invoke data_1 and data_2 both without freeing in between. When I free one of the resources in between or I invoke one method twice in a row the heap space is enough, as the garbage collector frees the memory. This works as I would expect it to work.
But when I invoke one data function, close the browser and hence the session, open a new browser and invoke one of the data functions again, then the application stops working as (I guess) the memory space is exceeded. The point is: the first session bean doesn't get destroyed and its @PreDestroy Method not invoked and therefore the ArrayList is still in the memory.
Can someone please explain to me what is going on here? Shouldn't a CDI bean be destroyed by the container as soon its context expires so that references can be set to null and the garbage collector can free resources?
I am using JBoss AS 7.1.1 and its default implementation JSF Mojarra 2.1.
Session beans (regardless CDI or JSF managed) stay alive until some session timeout exceeds (usually 30 minutes by default, dependent on application server), which you can specify in web.xml. Just closing the browser doesn't invalidate session and it wait to be destroyed by servlet container after timeout expiration. So, my assumption, such behaviour is just fine, @PreDestroy method will be invoked later.
The answer of @olexd basically explains what I was getting wrong in my mind, thank you very much! But invalidating the session after a determined period is not an option, so I had to use the comment of @geert3 as well, thank you for that! I am answering my own question to show how I have solved my particular problem in detail here.
What I was wrong about: I thought the session expires as soon as the browser is closed. This is wrong and it makes sense. One may want to close the browser and open it again to work in the same session as before.
For me this behaviour is not appropriate because I want to release resources as soon as the browser gets closed. So the answer is to manually invalidate the session like this:
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
As soon as this method is called, the @PreDestroy Method is called, exactly as I want it. Now I had to determine when to call this function. I searched for a way to listen to something like a browserclose event. There are the onbeforeunload and onunload events. onunload doesn't seem to work for me in Chrome, but the onbeforeunload does. See also this answer: https://stackoverflow.com/a/16677225/1566562
So I wrote a hidden button that gets clicked by javascript on beforeunload and invokes an appropriate backingbean method. This works as I would expect it to work. I tested it on Chrome 43.0.2357.65 and IE 11, for now I am content with it. However it doesn't work with onunload, but this is not of concern for me right now.
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;
}
}
Note that I have used JQuery. This works with JBoss AS 7.1.1 and the default Weld implementation as well.
One thing to add: one doesn't have to set all the referenes manually to null. This makes sense as well, as it would be tedious..
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With