package org.foo.leaky; import org.osgi.framework.*; /** * To run this example, first build it with ant then type: * * java -verbose:gc -jar launcher.jar bundles * * You should see the example start with GC trace enabled: * * [GC 896K->136K(5056K), 0.0029125 secs] * ... * -> * * If you try to update the bundle you should see a leak: * * [Full GC 320K->320K(5056K), 0.0280867 secs] * -> update 1 * [GC 16979K->16717K(21444K), 0.0014941 secs] * [Full GC 16717K->16714K(21444K), 0.0256443 secs] * -> update 1 * [GC 33378K->33118K(46424K), 0.0009662 secs] * [Full GC 33118K->33118K(46424K), 0.0268457 secs] * -> update 1 * [GC 49777K->49514K(59424K), 0.0009663 secs] * [Full GC 49514K->49463K(59424K), 0.0929226 secs] * [Full GC 49463K->49452K(65088K), 0.0452441 secs] * java.lang.OutOfMemoryError: Java heap space * -> * * Uncomment the call to remove(), rebuild and try again: * * [Full GC 320K->320K(5056K), 0.0238252 secs] * -> update 1 * [GC 16973K->16717K(21444K), 0.0021946 secs] * [Full GC 16717K->16717K(21444K), 0.0296025 secs] * -> update 1 * [GC 33373K->33114K(46428K), 0.0010646 secs] * [Full GC[Unloading class org.foo.leaky.Activator$Data] * [Unloading class org.foo.leaky.Activator$1] * [Unloading class org.foo.leaky.Activator] * 33114K->16721K(46428K), 0.0557759 secs] * * The leak is now gone and the classes are unloaded. */ public class Activator implements BundleActivator { /* * 8Mb data object */ static class Data { StringBuffer data = new StringBuffer(8 * 1024 * 1024); } /* * This example relies on a "feature" of the Java5 ThreadLocal implementation * where stale map entries are only cleared if set() or remove() is called on * another ThreadLocal for the same thread - and in the worst case even this * is not guaranteed to purge all stale map entries. * * As we shall soon see, missing out the remove() call in stop means that the * data object will be kept alive indefinitely because we don't use any other * ThreadLocal in our example. This in turn keeps our ClassLoader alive. * * Calling remove() in stop forces the underlying map entry to be cleared and * means the bundle's ClassLoader can now be collected on each update/refresh. */ static final ThreadLocal leak = new ThreadLocal() { protected Object initialValue() { return new Data(); }; }; public void start(BundleContext ctx) { leak.get(); } public void stop(BundleContext ctx) { // leak.remove(); } }