【发布时间】:2024-01-20 09:48:01
【问题描述】:
我有一个关于会话范围 CDI bean 生命周期的问题。
据我了解,会话范围内的 CDI bean 在会话开始时由容器构造,并在会话结束时被销毁。在销毁 bean 之前,将调用 @PreDestroy 方法,如https://docs.oracle.com/javaee/6/tutorial/doc/gmgkd.html 所述。它还说在这种方法中释放资源。
在我构建的 JSF 应用程序中,我遇到了内存泄漏,因为 bean 似乎没有被销毁,因此没有调用 @PreDestroy 方法来释放垃圾收集器的一些引用。所以我构建了一个简单的应用程序来测试行为。我的经验是,会话结束时会话 bean 不会被破坏,而且在需要内存空间时它甚至不会被破坏。我不敢相信我是第一个遇到这种情况的人,但我没有找到任何关于这种行为的信息..
所以我的问题是:CDI bean 不应该被销毁 - 因此 @PreDestroy 方法应该在其上下文过期后立即被调用吗?如果不应该至少在需要空间时将其销毁?
我的测试应用:
不允许发图,但是大纲是eclipse生成的非常基础的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 时超出了堆空间,而两者之间没有释放。当我释放其中的一个资源或连续两次调用一个方法时,堆空间就足够了,因为垃圾收集器释放了内存。这可以正常工作。
但是当我调用一个数据函数,关闭浏览器和会话,打开一个新的浏览器并再次调用其中一个数据函数时,应用程序停止工作,因为(我猜)内存空间被超出。关键是:第一个会话 bean 没有被销毁,它的 @PreDestroy 方法没有被调用,因此 ArrayList 仍在内存中。
有人可以向我解释一下这里发生了什么吗? CDI bean 不应该在其上下文过期后立即被容器销毁,以便可以将引用设置为 null 并且垃圾收集器可以释放资源吗?
我正在使用 JBoss AS 7.1.1 及其默认实现 JSF Mojarra 2.1。
【问题讨论】:
-
JBoss AS 7.1.1 是古老的。至少尝试当前的 Weld 版本,以排除已知且早已修复的错误。
-
好的,谢谢BalusC,我会试试然后回来!
-
我已将 WELD 实现升级到 1.1.23,但没有帮助。
-
之后我得到了wildfly 8.2.0并尝试在那里运行应用程序,但它与上面的jboss AS 7.1.1相同。它抛出一个 Java 堆空间 OutOfMemoryError
-
我只是想知道:我的想法错了吗?或者我是否正确假设 bean 应该在上下文到期后立即销毁?
标签: jsf cdi destroy weld session-scope