Skip to main content

Cleaning up an MBean when its resource dies

Posted by emcmanus on July 20, 2005 at 1:47 PM PDT

Suppose (to take my favourite example), you have some sort of
cache, and you want to be able to control it via an MBean. You
might have something a bit like this:

public interface CacheControlMBean {
    public int getSize();
}

public class CacheControl implements CacheControlMBean {
    public CacheControl(Cache cache) {
        this.cache = cache;
    }

    public int getSize() {
        return cache.getSize();
    }

    private final Cache cache;
}

This is a good pattern to follow, but it does have one
side-effect that might be unintended. Even if the
Cache is no longer referenced by anything else in the
application, it continues to be referenced by the
CacheControlMBean as long as that is in the MBean
Server. So when the Cache is no longer in use, we
need to make sure that the MBean is removed.

In my opinion, the best way to achieve this is to have an
explicit method that is called on the Cache when it
is to be destroyed. This method frees any resources (threads,
sockets, whatever) held by the Cache, and it also
unregisters the CacheControlMBean.

However, it is ugly for the Cache to contain
explicit logic to unregister its MBean. Much better is for it
to contain logic that allows its users to register a callback
that is invoked when the cache is destroyed. Then the
CacheControlMBean can register such a callback that
unregisters the MBean.

Here's what this might look like in the Cache:

public class Cache {
    ...
    public void addDestroyHandler(Runnable r) {
        destroyHandlers.add(r);
    }

    public void removeDestroyHandler(Runnable r) {
        destroyHandlers.remove(r);
    // Not an error if the handler wasn't there
    }

    public void destroy() {
        ...free resources...
        for (Runnable r : destroyHandlers)
            r.run();
    }

    private final List<Runnable> destroyHandlers = new ArrayList<Runnable>();
}

Now the CacheControlMBean can explicitly arrange
to be unregistered when the Cache is destroyed:

// This is the basic idea, but see below for modified version
public class CacheControl implements CacheControlMBean {
    public CacheControl(Cache cache) {
        this.cache = cache;
        cache.addDestroyHandler(new Runnable() {
            public void run() {
                unregisterMe();
            }
        });
    }
    ...
}

So how does the CacheControlMBean unregister
itself? The best way is to implement the

MBeanRegistration
interface to find out what
MBean Server and ObjectName to use. As I mentioned

before
, you can reasonably depend on the object not being
registered with more than one name.

public class CacheControl implements CacheControlMBean, MBeanRegistration {
    public CacheControl(Cache cache) {
        this.cache = cache;
    }
    ...
    public synchronized ObjectName preRegister(MBeanServer mbs, ObjectName name)
            throws InstanceAlreadyExistsException {
        if (objectName != null) {
            throw new InstanceAlreadyExistsException("Already registered as: " +
                             objectName);
        }
        mbeanServer = mbs;
        objectName = name;
    }

    public void postRegister(Boolean ok) {
        if (ok)
            cache.addDestroyHandler(destroyHandler);
        else
            preDeregister();
    }

    public synchronized void preDeregister() {
    cache.removeDestroyHandler(destroyHandler);
    mbeanServer = null;
        objectName = null;
    }

    public void postDeregister() {
    }

    private synchronized void unregisterMe() {
        if (objectName != null) {
        try {
        mbeanServer.unregisterMBean(objectName);
        } catch (InstanceNotFoundException e) {
        // log the exception somewhere...
        }
    }
    }

    private final Runnable destroyHandler = new Runnable() {
        public void run() {
        unregisterMe();
        }
    };
    private MBeanServer mbeanServer;
    private ObjectName objectName;
    private final Cache cache;
}
Weak MBeans

The above is a good solution if you know that the
Cache will be explicitly destroyed at the right
time. But sometimes it is hard to determine the right time to
destroy a resource like the Cache. For example,
the right time to destroy the cache might be when it has no more
users and there are no more ongoing operations on it. That can
be quite hard to track.

An alternative is to arrange for the Cache to be
destroyed when nobody references it any more. The garbage
collector can tell you that. For example, the
Cache class could have a

finalize()
method that calls
destroy().

This is usually a bad idea, because you don't know how long
will elapse between the moment when the Cache is no
longer referenced and the moment when its
finalize() method is called. It could be hours or
even days. If the Cache holds resources like
threads or connections, they will be wasted during those hours
or days.

On the other hand, if the Cache doesn't hold any
important resources, relying on garbage collection remains an
option. But notice that the CacheControlMBean
references a Cache. As long as the MBean is
registered, the Cache will never be garbage
collected.

You can get around this by using a

WeakReference
. A WeakReference
wraps a reference to another object (in our case, the
Cache), but if it is the only reference to the
object then the object can be garbage collected anyway, and
the reference becomes null. This is also true if there are
several WeakReferences and not just one.

Here's how we could rewrite CacheControl to use a
WeakReference and so not prevent the
Cache from being garbage collected.

public class CacheControl implements CacheControlMBean, MBeanRegistration {
    public CacheControl(Cache cache) {
    this.cacheRef = new WeakReference<Cache>(cache);
    }

    public int getSize() {
    Cache c = cacheRef.get();
    if (c == null) {
            unregisterMe();
        throw new IllegalStateException("Cache no longer exists");
    }
        return c.getSize();
    }
    ...
    private final WeakReference<Cache> cacheRef;
}

If you think the MBean is the only code that is going to be
interested in knowing when the Cache disappears,
then you could get rid of the addDestroyHandler
method from the Cache class and its
finalize method. The code above makes sure that
the MBean will disappear after (a) the Cache gets
garbage collected and (b) you then call some method like
getSize() on the MBean.

Of course both (a) and (b) represent arbitrary delays. You
could get rid of the arbitrary delay before (b) by periodically
checking whether the WeakReference is still valid.
For example you could use a

Timer
to do this.

Polling with a Timer wastes time when the
reference is still valid, and fails to react immediately once it
no longer is. You can arrange to react punctually as soon as
the Cache is garbage collected by using a

ReferenceQueue
. We're getting into some
fairly advanced Java usage here!

public class CacheControl implements CacheControlMBean, MBeanRegistration {
    public CacheControl(Cache cache) {
    this.cacheRef = new CacheReference(cache);
    }
    ...
    private class CacheReference extends WeakReference<Cache> {
CacheReference(Cache cache) {
    super(cache, refQueue);
}

CacheControl getCacheControl() {
    return CacheControl.this;
}
    }

    private static final ReferenceQueue<Cache> refQueue =
new ReferenceQueue<Cache>();
    static {
Runnable r = new Runnable() {
    public void run() {
while (true) {
    CacheReference ref;
    try {
ref = (CacheReference) (WeakReference<Cache>)
    refQueue.remove();
    } catch (InterruptedException e) {
return;
    }
            ref.getCacheControl().unregisterMe();
}
    }
};
new Thread(r, "CacheControl cleaner").start();
    }
    ...
    private final CacheReference cacheRef;
}

This creates a thread that will wait for the reference to be
cleared. (The Timer solution also created a
thread, even though that was less obvious.) The same thread is
shared between all CacheControl MBeans that might
exist for different Caches.

It's been suggested that the JMX API could have some explicit
support for "Weak MBeans" like this. I'm not sure there's
enough use for them to justify including them in the API, and
I'm also not sure what a general-purpose API for Weak MBeans
would look like. But the above shows how you can create your
own Weak MBeans if need be.


The next entry
talks about a way to combine WeakReference
with dynamic proxies, which you can use to solve
problems of this sort, including problems that are unrelated
to the JMX API.

Related Topics >>