19.06.2020 | Fabian König | comment icon 0 Comment

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:

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:

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:

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):

2.2   EJBs

For EJBs, manual modification of the bean lifetime is prohibited. Instead, it is possible to inject the same class directly:

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

Left: Heap size constantly increases due to memory leak. Right: When dynamically injected instances are manually destroyed, the heap size stays constant (the sudden drop in heap size indicates a garbage collection event).

A convenient helper method that takes care of destroying the instance could be implemented like this:

cdi dependency injection java ee memory leak out of memory weld

Leave a Comment