当前位置: 首页 > 知识库问答 >
问题:

CDI会话作用域bean未被破坏导致内存泄漏

商高谊
2023-03-14

我有一个关于会话范围的CDIBeans的生命周期的问题
据我所知,会话范围的CDIBean在会话启动时由容器构造,在会话结束时由容器销毁。在销毁bean之前,将调用@PreDestroy方法,如下所述https://docs.oracle.com/javaee/6/tutorial/doc/gmgkd.html.它还说用这种方法释放资源

在JSF应用程序中,我构建时遇到内存泄漏,因为bean似乎没有被破坏,因此没有调用@PreDestroy方法来释放垃圾收集器的一些引用。因此,我构建了一个简单的应用程序来测试该行为。我的经验是,会话bean在会话结束时不会被破坏,而且在需要内存空间时也不会被破坏。我不敢相信我是第一个遇到这种情况的人,但我没有找到任何关于这种行为的信息。。

所以我的问题是:CDIBean不应该在其上下文过期后立即被销毁,从而调用@PreDestroy方法吗?如果不是的话,它至少应该在需要空间的时候被摧毁吗?

我的测试申请:

我不允许发布图片,但大纲是eclipse生成的非常基本的JSFWebApp。我也有豆子。xml文件。

测验爪哇:

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。爪哇:

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>

我打开索引。xhtml页面和@PostConstruct方法按预期调用。当我调用data_1和data_2时,堆空间被超出,而不在两者之间释放。当我释放中间的一个资源或一行调用一个方法两次时,堆空间就足够了,因为垃圾收集器会释放内存。这就像我期望的那样有效。

但是,当我调用一个数据函数时,关闭浏览器和会话,打开一个新浏览器并再次调用其中一个数据函数,然后应用程序停止工作,因为(我猜)内存空间已超出。关键是:第一个会话bean不会被销毁,它的@PreDestroy方法也不会被调用,因此ArrayList仍然在内存中。

有人能给我解释一下这里发生了什么事吗?CDIBean不应该在其上下文过期后立即被容器销毁,以便将引用设置为null,垃圾收集器可以释放资源吗
我正在使用JBoss AS 7.1。1及其默认实现JSF Mojarra 2.1。

共有2个答案

顾淳
2023-03-14

@olexd的答案基本上解释了我的想法,非常感谢!但是在确定的时间段后使会话无效不是一个选项,所以我也不得不使用@geert3的注释,谢谢!我在这里回答我自己的问题,以展示我是如何详细解决我的特定问题的。

我错在:我以为一旦浏览器关闭,会话就会过期。这是错误的,也是有道理的。您可能希望关闭浏览器并再次打开它,以便在与以前相同的会话中工作
对我来说,这种行为不合适,因为我想在浏览器关闭后立即释放资源。因此,答案是手动使会话无效,如下所示:

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

一旦这个方法被调用,@PreDestroy方法就会被调用,这正是我想要的。现在我必须确定何时调用这个函数。我寻找一种方法来收听类似于浏览器关闭事件的内容。onunload在Chrome似乎对我不起作用,但是onbepreunload起作用。另见这个答案:https://stackoverflow.com/a/16677225/1566562

所以我写了一个隐藏的按钮,在卸载之前被javascript点击,并调用一个合适的backingbean方法。这和我期望的一样有效。我在Chrome43.0.2357.65和IE11上测试了它,现在我对它很满意。然而,它不适用于onunload,但这不是我现在关心的问题。

因此,我的最终代码如下所示:

指数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>

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

}

注意,我使用了JQuery。这适用于JBossAS 7.1。1以及默认的焊接实现
需要添加一点:不必手动将所有引用设置为null。这也是有道理的,因为这会很乏味。。

云新知
2023-03-14

会话bean(无论CDI或JSF管理)保持存活,直到某些会话超时超过(默认情况下通常为30分钟,取决于应用程序服务器),您可以在web.xml.中指定仅关闭浏览器不会使会话无效,它等待被销毁超时到期后的Servlet容器。所以,我的假设是,这样的行为很好,@PreDestroy方法将在以后调用。

 类似资料:
  • 恐怕这个问题会有点模糊,但这是... 我们注意到我们的JEE7 web应用程序中存在一些非常奇怪的、偶然的行为。有时,用户页面会突然开始显示来自完全不同用户会话的数据!到目前为止,我还没有能够复制这种现象,也没有在日志中找到任何问题的迹象,但是似乎一个用户的页面开始显示存储在@SessionScoped CDIBean中的数据,该CDIBean应该属于另一个用户的会话。 这种行为对任何人都有影响吗

  • MyCart.java 这个MyCart Bean是每个HTTP会话实例化的名为CDI的Bean。 抽象类 问题描述 仅用于理解会话范围的CDI。我有两个JSP文件,如下所示 SetCDIBeanValue.jsp其中,我将获得命名会话CDI Bean(MyCart)的实例将It String属性的值设置为String值FROM_FIRST_JSP 因为这个会话的作用域是有限的,所以我想应该只有一

  • 我必须添加一个WordPress安装到我的CodeIgniter系统,所以我把它放在一个名为的子图中,并在我的中排除了该文件夹。一切都很好。 我已经将所有的WordPress表和放在我的CodeIgniter数据库中,前缀为。 我现在已经将WordPress博客头文件加载到,就像这样; 并在我的控制器中创建了一个注册方法,以实际链接到我的。我这样做是因为我想使WordPress登录/注册过时,并从

  • 我有一些和等,但是我没有设置。你觉得会是那样吗? 你有什么想法或建议吗? PS:该应用程序在Ubuntu机器上运行 多谢.

  • 我有一个CDI bean,定义如下: 现在,我想以编程方式将其从会话范围中删除: 但是变量始终为空。如何检索实例并销毁它?

  • 我有一个应用程序,它有两个ApplicationContexts和,其中是的父上下文覆盖中的一些bean,但也使用它的一些bean。对于前端,我使用Wicket,我有两个Wicket应用程序和,它们使用各自的Spring应用程序上下文。 现在问题出现在会话范围的bean,对于它我有两种不同的实现:定义一个工厂方法,返回一个的实例,有一个工厂方法,该工厂方法具有相同的签名,返回一个的实例。用户现在打