How to create and destroy CDI (Weld) Managed Beans via the BeanManager?

Ben Kirby picture Ben Kirby · Dec 7, 2011 · Viewed 15.1k times · Source

I'm trying to create instances of CDI managed beans using the BeanManager rather than Instance .select().get().

This was suggested as a workaround to an issue I've been having with ApplicationScoped beans and garbage collection of their dependents - see CDI Application and Dependent scopes can conspire to impact garbage collection? for background and this suggested workaround.

If you use the Instance programmatic lookup method on an ApplicationScoped bean, the Instance object and any beans you get from it are all ultimately dependent on the ApplicationScoped bean, and therefore share it's lifecycle. If you create beans with the BeanManager, however, you have a handle on the Bean instance itself, and apparently can explicitly destroy it, which I understand means it will be GCed.

My current approach is to create the bean within a BeanManagerUtil class, and return a composite object of Bean, instance, and CreationalContext:

public class BeanManagerUtil {

    @Inject private BeanManager beanManager;

    @SuppressWarnings("unchecked")
    public <T> DestructibleBeanInstance<T> getDestructibleBeanInstance(final Class<T> type,
            final Annotation... qualifiers) {

        DestructibleBeanInstance<T> result = null;
        Bean<T> bean = (Bean<T>) beanManager.resolve(beanManager.getBeans(type, qualifiers));
        if (bean != null) {
            CreationalContext<T> creationalContext = beanManager.createCreationalContext(bean);
            if (creationalContext != null) {
                T instance = bean.create(creationalContext);
                result = new DestructibleBeanInstance<T>(instance, bean, creationalContext);
            }
        }
        return result;
    }
}

public class DestructibleBeanInstance<T> {

    private T instance;
    private Bean<T> bean;
    private CreationalContext<T> context;

    public DestructibleBeanInstance(T instance, Bean<T> bean, CreationalContext<T> context) {
        this.instance = instance;
        this.bean = bean;
        this.context = context;
    }

    public T getInstance() {
        return instance;
    }    

    public void destroy() {
        bean.destroy(instance, context);
    }
}

From this, in the calling code, I can then get the actual instance, put it in a map for later retrieval, and use as normal:

private Map<Worker, DestructibleBeanInstance<Worker>> beansByTheirWorkers =
    new HashMap<Worker, DestructibleBeanInstance<Worker>>();
...
DestructibleBeanInstance<Worker> destructible =
        beanUtils.getDestructibleBeanInstance(Worker.class, workerBindingQualifier);
Worker worker = destructible.getInstance();
...

When I'm done with it, I can lookup the destructible wrapper and call destroy() on it, and the bean and its dependents should be cleaned up:

DestructibleBeanInstance<JamWorker> workerBean =
        beansByTheirWorkers.remove(worker);
workerBean.destroy();
worker = null;

However, after running several workers and leaving my JBoss (7.1.0.Alpha1-SNAPSHOT) for 20 minutes or so, I can see GC occurring

2011.002: [GC
Desired survivor size 15794176 bytes, new threshold 1 (max 15)
1884205K->1568621K(3128704K), 0.0091281 secs]

Yet a JMAP histogram still shows the old workers and their dependent instances hanging around, unGCed. What am I missing?

Through debugging, I can see that the context field of the bean created has the contextual of the correct Worker type, no incompleteInstances and no parentDependentInstances. It has a number of dependentInstances, which are as expected from the fields on the worker.

One of these fields on the Worker is actually an Instance, and when I compare this field with that of a Worker retrieved via programmatic Instance lookup, they have a slightly different CreationalContext makeup. The Instance field on the Worker looked up via Instance has the worker itself under incompleteInstances, whereas the Instance field on the Worker retrieved from the BeanManager doesn't. They both have identical parentDependentInstances and dependentInstances.

This suggests to me that I haven't mirrored the retrieval of the instance correctly. Could this be contributing to the lack of destruction?

Finally, when debugging, I can see bean.destroy() being called in my DestructibleBeanInstance.destroy(), and this goes through to ManagedBean.destroy, and I can see dependent objects being destroyed as part of the .release(). However they still don't get garbage collected!

Any help on this would be very much appreciated! Thanks.

Answer

LightGuard picture LightGuard · Dec 8, 2011

I'd change a couple of things in the code you pasted.

  1. Make that class a regular java class, no injection and pass in the BeanManager. Something could be messing up that way. It's not likely, but possibly.
  2. Create a new CreationalContext by using BeanManager.createCreationContext(null) which will give you essentially a dependent scope which you can release when you're done by calling CreationalContext.release().

You may be able to get everything to work correctly the way you want by calling the release method on the CreationalContext you already have in the DestructibleBeanInstance, assuming there's no other Beans in that CreationalContext that would mess up your application. Try that first and see if it messes things up.