Don’t get trapped into a memory leak using CDI Instance Injection

In one of our recent projects we have encountered some memory leaks using standard JavaEE technologies like CDI and EJBs. Our application in question does a lot of communication using JMS as a transportation layer. To be able to handle different message types dynamically we have used the Instance Injection of CDI. Using that approach with CDI might get your trapped into some memory leak problems like we did, so we would like to share our experiences and what you can do about it.

The Problem

Our application should be able to dynamically handle different message types thats why we have choosen the following injection approach:

@Stateless
public class ServiceDispatcher {
  @Inject
  private Instance<Handler> handlers;

  public String handleMessage(String message, String messageType) {
    Instance<Handler> bean = handlers.select( new MessageTypeQualifier() {
      @Override
      public String type() {
        return messageType;
      }
  });
  return bean.get().handle( message );
}

If you create CDI bean instances programmatically like in the example above you should be aware that your have to care about the lifecycle of the bean instances by yourself. All created instances are automatically in dependent scope. So these created instances are living as long as the bean instance they are injected in. You should be alerted if you do that programmatic instance creation in some long running bean like application- or sessionscoped ones.

So in our case at first glance you would check the surrounding bean which is a Stateless Session Bean (SLSB). So one might be feeling safe here, since these kind of beans do have a very narrow lifespan by definition. But this assumption will get you trapped because of most application server will have pooling for your SLSB activated, like Wildfly 10.
With the SLSB pooling enabled the instance of a SLSB will not get caught by  the garbage collection but reused every time. This means all created CDI bean instances inside this SLSB will not be destroyed by the GC but rather lead to some nice memory leak over time.

We will present you different solutions and workaround for that particular problem.

Solutions

Solution 1: Disable SLSB Pooling in your Application Server

Disabling pooling at all you should doing fine and all bean instances should be get destroyed by the garbage collection. But you will loose some performance improvements on the other side. So this might be a quick workaround only.

Solution 2: Using CDI 1.1 new API

With CDI 1.1 (introduced in JavaEE 7) there was some API changes which are addressed this problem. Now you can easily destroy your created bean instances via an API call:

public String handleMessageLeakFixedCDI11( final String message, final String messageType) {
  Instance<Handler> bean = handlers.select( new MessageTypeQualifier() {
    @Override
    public String type() {
        return messageType;
    }
  });
  Handler handler = bean.get();
  String result = handler.handle( message );
  bean.destroy(handler); // added in CDI 1.1
  return result;
}

Solution 3: Using CreationalContext in CDI1.0

CDI 1.0 (which is part of JavaEE 6) unfortunately does not provide any easy approach to get rid of your created bean instances. But there is a workaround where you create your own CreationalContext which you can release after using your bean instance and will destroy your created instances as well:

public String handleMessageLeakFixedCDI10( final String message, final String messageType) {
  Set<Bean<?>> beans = beanManager.getBeans( Handler.class, new MessageTypeQualifier() {
    @Override
    public String type() {
        return messageType;
    }
  });
  Bean<Handler> bean = (Bean<Handler>) beans.iterator().next();
  CreationalContext<?> creationalContext = beanManager.createCreationalContext( bean );
  Handler handler = (Handler) beanManager.getReference( bean, Handler.class, creationalContext);
  String result = handler.handle( message );
  creationalContext.release();

  return result;
}

Example application

If you want to try these approaches by yourself we provide some sample project where you can play around in depth. The example application can be found here:

https://github.com/akquinet/cdi-ejb-memoryleak

This will automatically do the following:

  • Download the latest Wildfly 10 Version
  • Start Wildfly
  • Build and Deploy the sample application providing REST endpoints for the different approaches
  • Run some Tests which will show impact of the different approaches

For further instruction please have a look at the readme file inside the project folder.

To see how memory consumption evolves you have to connect your favorite Java profiler (Visual-VM will do fine here) to monitor your heap memory.

This is the the heap memory consumption after run the test configuration which will lead to a memory leak :

memoryleak

You can see even after 5 Minutes running time the minimum heap usage increasing (from roughly 100MB up to 250MB).

Doing the same run using a fixed implementation will show that kind of memory figure:

memoryleak-fix

Conclusion

Whenever you are using the CDI Instance injection approach be careful about created bean instances and dispose them explicitly choosing one of the presented approaches. Sometimes its not really clear how long a bean instance is running eventually. Even if you are sure to have your lifecycles and lifespan of your beans understood and fixed providing explicit disposing will protect you against refactorings (introducing hidden changes to lifecycles, lifespans) and reconfigurations (enabling a former disabled SLSB pool etc.) in the future.

3 thoughts on “Don’t get trapped into a memory leak using CDI Instance Injection

    1. Hi,

      no “normal” injection is not a problem at all. Here you will have just one instance of a bean or a proxy instance of a bean be bound the the caller beans context. The problem in our scenario is that we create a concrete bean instance on every call having a increasing number of hold instances over time.

  1. Hey Daniel,

    thank you for sharing this. I ran into the same issue and could easily solve it with the help of your article. I wasn’t aware that the instance.destroy()-method exists and was needed.

    Best regards
    Marc

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s