Memory Leak when calling @Destroy (with SFSBs)
daniell Jan 3, 2012 6:41 AMHi!
I've discovered a memory leak in our application and I think it is a bug in Seam. Here we go:
We're using Seam 2.2.2.Final deployed in an EAR on JBoss AS 5.1.0.GA.
In an example-project, we have 2 Seam components, one POJO and one SFSB. The POJO-component uses the SFSB-component via Seam-DI (@In). The POJO-component has a method annotated with @Destroy.
We are using the POJO-component in a (long-running-)conversation. Thus, the TestComponentA as well as the TestEJB will be created.
When the conversation is ended and all components will be removed, the @Destroy-methods of all seam-components are called one after each other. The order, the @Destroy-methods are called, depend on the hashCode of the seam-name of the components (the component-names are put into an java.util.HashSet in the method org.jboss.seam.contexts.ServerConversationContext.getNamesFromSession() - the for-loop of the method org.jboss.seam.contexts.Contexts.destroy iterates over this HashSet afterwards).
In the following example, the component-names have been chosen in this way, that the TestEJB will be removed before the TestComponentA is removed. So see the call-sequence:
- the SFSB-component is destroyed by seam - the container removes the SFSB
- the @Destroy-method of the POJO-component is called. But before, Seam-DI creates a new SFSB-component TestEJB because of the @In-annotation
- the POJO-component is removed by the GC
- the SFSB-component is still present
It is easy to see that there are unused SFSBs via the jmx-console or in the server-log:
10:56:31,693 ERROR [STDERR] TestEJB.postConstruct() called 10:56:31,693 ERROR [STDERR] tca created [...] 10:59:36,518 ERROR [STDERR] TestEJB.discard() called 10:59:36,518 ERROR [STDERR] TestEJB.preDestroy() called 10:59:36,519 ERROR [STDERR] TestEJB.postConstruct() called 10:59:36,519 ERROR [STDERR] tca destroyed
To reproduce the bug, 2 simple Components have been created. This is the SFSB-Component:
@Stateful @Name(TestEJB.SEAM_NAME) @Interceptors(SeamInterceptor.class) @JndiName(TestEJBLocal.JNDI_NAME) @Scope(ScopeType.CONVERSATION) @AutoCreate public class TestEJB implements TestEJBLocal { public static final String SEAM_NAME = "tcb"; @Remove public void discard() { System.err.println("TestEJB.discard() called"); } @PostConstruct public void postConstruct() { System.err.println("TestEJB.postConstruct() called"); } @PreDestroy protected void preDestroy() { System.err.println("TestEJB.preDestroy() called"); } }
...and it's local-interface:
@Local public interface TestEJBLocal { /** The local JNDI name for this EJB. */ final static String JNDI_NAME = "ejb/project/TestEJB/local"; public void discard(); }
Last but not least the POJO-Component:
@Scope(ScopeType.CONVERSATION) @Name(TestComponentA.SEAM_NAME) @AutoCreate public class TestComponentA implements Serializable { public static final String SEAM_NAME = "tcd"; private static final long serialVersionUID = 7550180731244593255L; @In(value=TestEJB.SEAM_NAME) private TestEJBLocal testEJB; @Create public void create() { System.err.println("tca created"); } @Destroy public void destroy() { System.err.println("tca destroyed"); } }