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 theCache is no longer referenced by anything else in the application, it continues to be referenced by theCacheControlMBean 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 theCacheControlMBean 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 theCache 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 callsdestroy().

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 theCache 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 CacheControlMBeanreferences 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, theCache), 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 severalWeakReferences and not just one.

Here's how we could rewrite CacheControl to use aWeakReference and so not prevent theCache 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 theCache 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 combineWeakReference with dynamic proxies, which you can use to solve problems of this sort, including problems that are unrelated to the JMX API.