Memory leaks from CDI
The usual way of dependency injection with CDI using @Inject
offers an extremely efficient mechanism to supply an object with its dependencies. However, there are scenarios where a dynamic version of dependency injection is required. For those cases, CDI offers javax.enterprise.inject.Instance
, an interface that allows for the dynamic injection of new instances at runtime on a per-request basis. In one of our current projects, we have encountered a serious memory leak while using CDI’s dynamic dependency injection. The root cause of this problem and a solution to it are presented in the following article.
Self-Injection
In our specific use case, we wanted to invoke an annotated business method from within the same class. Consider the following scenario:
1 2 3 4 5 6 7 8 9 10 11 12 |
@Stateless, @Singleton or @Dependent class AService { public void methodA() { asyncMethod(); // static call to this.asyncMethod(); } @Asynchronous public void asyncMethod() { // do some tedious work } } |
When methodA
is invoked, the inner call to asyncMethod
can not be intercepted by the container, and thus asyncMethod
is being called synchronously despite the @Asynchronous
annotation. This is because the call to asyncMethod
is called on the same object: it is implicitly invoked on this
.
In order for the asynchronous annotation to have any effect, the call to asyncMethod
must be invoked on an injected object. Objects injected by CDI are in fact proxies that decorate the real object. By invoking asyncMethod
on a proxy, the proxy object obtains a new thread and lets the real asyncMethod
run on this thread. With the help of Instance
, we are able to dynamically inject a new proxy of the same class AService
and invoke asyncMethod
on that proxy. As a consequence, asyncMethod
is indeed invoked asynchronously:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Stateless, @Singleton or @Dependent class AService { @Inject private Instance<AService> selfProvider; public void methodA() { selfProvider.get().asyncMethod(); } @Asynchronous public void asyncMethod() { // do some tedious work } } |
The problem – lifetime of injected instances
The problem becomes apparent as soon as one understands that the dynamically injected instance is in the @Dependent
scope, which irrevocably binds the lifecylce of the injected object to the lifecycle of the object it is injected into. If the object of class AService
has a long lifetime, the dynamically injected instance will remain on the heap, even if methodA
has returned and any references to the injected instance have become inaccessible to the programmer. This inevitably generates a memory leak.
In our case, using Weld as the CDI implementation, we looped over tens of thousands of entries within methodA
, each time injecting a new proxy of class AService
. Analyses of heap dumps revealed that tens of thousands of Weld proxy objects (of the following signature: AService$Proxy$_$$_Weld$EnterpriseProxy$
) were indeed located in memory without being garbage collected.
Solution 1
The simplest solution to this problem is to avoid self-injection at all and to employ a controller – worker pattern. Annotated business methods are implemented in a worker class. A controller class injects the worker class and invokes the business methods of that worker class on the injected proxy:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@Stateless, @Singleton or @Dependent class AControl { @Inject private AWorker worker; public void methodA() { worker.annotatedMethod1(); worker.annotatedMethod2(); } } @Stateless, @Singleton or @Dependent class AWorker { @Asynchronous public void annotatedMethod1() { // do business logic... } @Transactional(REQUIRES_NEW) public void annotatedMethod2() { // do business logic... } } |
Solution 2
If one wants to hold on to self-injection, a more careful management of the lifecycle of the injected instance is required. At this point, the procedure depends on the type of the instance that is being injected.
2.1 @Dependent beans
For @Dependent
beans the injected instance must be explicitly destroyed. This is possible since CDI1.1, which has been introduced in JavaEE 7 (exception handling omitted for brevity):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Dependent class AService { @Inject private Instance<AService> selfProvider; public void methodA() { AService self = selfProvider.get(); self.asyncMethod(); selfProvider.destroy(self); } @Asynchronous public void asyncMethod() { // do some tedious work } } |
2.2 EJBs
For EJBs, manual modification of the bean lifetime is prohibited. Instead, it is possible to inject the same class directly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@Stateless or @Singleton class AService { @Inject private AService self; public void methodA() { self.asyncMethod(); } @Asynchronous public void asyncMethod() { // do some tedious work } } |
This might look like an infinite dependency injection loop which eventually results in a StackOverflowError. However, remember that Java EE merely injects a proxy of AService
. This even works in case of a Singleton, where the proxy delegates to the very same (in the sense of Object.equals()
) instance of AService
.
Summary
Dynamic dependency injection with CDI is a great feature and helps to inject a proxy of itself, thus allowing to call annotated business methods from within the same class. However, the lifecycle of the injected instances has to be explicitly managed by the programmer. Otherwise, serious memory leaks may occur. If your problems with memory leaks related to CDI’s dynamic dependency injection are still not resolved, please have a look into the following references:
– Weld Specification Documentation on Instance API
– Weld-2416 Ticket on memory behaviour of Instance API
– Discussion on JBossDeveloper
– Blog post akquinet on memory leaks related to CDI’s Instance
Appendix

A convenient helper method that takes care of destroying the instance could be implemented like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public enum Cdi { ; public static <T, R, E extends Exception> R onInstance(Instance<T> instance, Function<T, R> operation) throws E { T t = instance.get(); try { return operation.apply(t); } finally { try { instance.destroy(t); } catch (Exception ex) { // do some logging } } } } |
Leave a Comment