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