11th Jan 2008 by Mark Turansky

HOW TO: Bootstrap Java programs in isolated classloaders

Filed under Filed under Code Hints, Engineering, HOW TO

Bootstrapping is the process by which you load a very small and very simple pure java program with no dependencies that, in turn, loads, configures, and runs more complex programs with varying dependencies. Bootstrapping lets you run your container without polluting the system classpath. This allows you to run your deployed applications with the unpolluted system classpath as its parent. You’ve achieved classloader isolation.

When would you want to bootstrap? Any time you want an unpolluted system classpath, which I’m finding is often convenient.

Let’s say you want to write some kind of middleware product, a container of some sort that deploys other applications within it. You will run into classloading issues. The dependencies that your container has (say, Spring 2.0.6) may not be what your deployed application requires (maybe, Spring 1.2.6). You will find that you cannot have commons-logging in both applications (container and child). There are many ways to encounter java.lang.LinkageErrors. It’s very easy to cross the streams when running in a mutli-app environment.

What you want to do is load your container and deployed apps in splendid isolation from each other. How do you do that? Bootstrapping!

Here’s how you bootstrap…


import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;

public class Bootstrap {

    public static void main(String[] args) throws Exception {

        /*
            Assume your application has a "home" directory
            with /classes and /lib beneath it, where you can put
            loose files and jars.

            Thus,

            /usr/local/src/APP
            /usr/local/src/APP/classes
            /usr/local/src/APP/lib
         */

        String HOME = "/usr/local/src/YOURAPP";
        String CLASSES = HOME + "/classes";
        String LIB = HOME + "/lib";

        // add the classes dir and each jar in lib to a List of URLs.
        List urls = new ArrayList();
        urls.add(new File(CLASSES).toURL());
        for (File f : new File(LIB).listFiles()) {
            urls.add(f.toURL());
        }

        // feed your URLs to a URLClassLoader!
        ClassLoader classloader =
                new URLClassLoader(
                        urls.toArray(new URL[0]),
                        ClassLoader.getSystemClassLoader().getParent());

        // relative to that classloader, find the main class
        // you want to bootstrap, which is the first cmd line arg
        Class mainClass = classloader.loadClass(args[0]);
        Method main = mainClass.getMethod(”main”,
                new Class[]{args.getClass()});

        // well-behaved Java packages work relative to the
        // context classloader.  Others don’t (like commons-logging)
        Thread.currentThread().setContextClassLoader(classloader);

        // you want to prune the first arg because its your main class.
        // you want to pass the remaining args as the “real” args to your main
        String[] nextArgs = new String[args.length - 1];
        System.arraycopy(args, 1, nextArgs, 0, nextArgs.length);
        main.invoke(null, new Object[] { nextArgs });
    }

}

You can try this code out for yourself. Cut & paste the bootstrap code above into your favorite IDE, put that single Bootstrap.class onto your classpath, and run it like so:


java -cp . Bootstrap sample.HelloWorldMain Hello!
.

Click here to download the sample /usr/local/src/YOURAPP application.
Tip for Windows users, you can make the path c:\usr\local\src\YOURAPP it’ll work.

What's next? CommentsLeave a comment Digg it Save This Page

15 Responses to “HOW TO: Bootstrap Java programs in isolated classloaders”

  1. Dan Says:

    You forgot to set the context classloader before the main invocation, NEWB!

  2. Mark Turansky Says:

    It’s funny ’cause it’s true! Nice catch, Dan.

    The above code works just fine except that you *will* want to add this single line before you reflectively invoke the main method:


    Thread.currentThread().setContextClassLoader(classloader);

    Lots of well-written Java software use the thread’s context classloader to resolve classes and classpath-based resources. This is a very effective way to avoid crossing the streams between classloaders.

    Other software packages don’t always play as nicely with classloaders, even when you set the context classloader. Bootstrapping helps in this case, but well-behaved software would be even better.

    FYI: Dan is one hell of a smart developer, groks all the bootstrapping and classloading challenges you’re likely to face, and — best of all — works for me on whatever madness our bosses let us pursue :)

  3. Casper Says:

    I used a similar approach to make a JSR-296 app restartable (for changing language), but it did not seem to work in a Web Start scenario so I gave up on that. Will this work in such an environment?

  4. Mark Turansky Says:

    Casper,

    Thanks for the inquiry. Short answer, I don’t know.

    I’m unfamiliar with Web Start. I don’t know how to bootstrap in that scenario.

    I work mostly with web applications and back-end systems (messaging/integration). I know we can boostrap Jetty as part of our system as well as all the disparate components deployed around the network.

    I hope the code I posted can help you figure out how to bootstrap a Web Start app. If you figure it out, please post the solution online! I’d love to see it.

    Mark

  5. Casper Says:

    Hello Mark,

    I gave it another shot, motivated by this blog entry. It poses a few problems though, the JNLPClassLoader is package private in com.sun.jnlp namespace and hence completely inaccessable. But by casting to an URLClassLoader, the remote CLASSES (in fact, works for non-jws execution too) can be fetched and passed along to the new ClassLoader instance.

    URLClassLoader jnlpClassLoader = (URLClassLoader)this.getClass().getClassLoader();
    ClassLoader bootstrapClassLoader = new URLClassLoader( jnlpClassLoader.getURLs());

    …and that works, but causes issues with the security manager later on. Another approach:

    URLClassLoader jnlpClassLoader = (URLClassLoader)this.getClass().getClassLoader();
    URLClassLoader classLoader = URLClassLoader.newInstance( jnlpClassLoader .getURLs(), jnlpClassLoader);

    …seems to get slightly further, but causing errors on the EDT. I feel I’m getting close, but I am not sure how to attempt this off the EDT or possibly simply empty the EDT first. Now I remember why I am no fan of classloaders! :D

    /Casper

  6. » Terracotta Server as a Message Bus Says:

    […] way to cluster standalone Spring apps. The named classloader did the trick for us. You will need to bootstrap your application to make this work. You should probably be doing this […]

  7. skanga Says:

    I always hate seeing hard coded unix filename conventions in Java code. Using File.separatorChar is a much better idea.

  8. Mark Turansky Says:

    This was just an example program to learn from. I presume that people reading this HOW TO would understand to set their HOME variable from the outside. Maybe they’d use a command line argument to their bootstrapper, maybe a -D system property. Either way, you could then use an environment-specific path that’s appropriate for your deployment without string concatentation or File.separatorChar.

  9. Christian Bohm Says:

    Hi guys! Nice trick.
    My question is…. will this work in a swing application?
    I mean… I have a swing application that calls to another swing application using the main method.
    I want the caller application be in a separated classloader than the invoked one.
    The problem is that the current Thread is the swing thread so if I modify swing thread’s classloader, it will be modified for both applications. Right?

  10. » No one should work alone. Ever. Says:

    […] and deployment them as separate components on our message bus. All our components are deployed in isolated classloaders, which will solve their problem. The older issue involved the constant instantiation/initialization […]

  11. scm Says:

    Hi, what do you mean by an “unpolluted system classpath”?

  12. Mark Turansky Says:

    @scm,

    Whenever you start the JVM with a “-cp” argument, all those jars and libs on your classpath are on the system classpath. Any code you run in that JVM will have those libs in their classloader.

    When you’re writing middleware or some kind of container, you’ll run into a problem where your container might need one version of a library but the application you want to deploy has the same library but a different version. You need bootstrapping to reconcile this. The code example I gave shows how you would set the system classloader (which is empty, because you’re bootstrapping) as the parent classloader to the application you want to deploy. This leaves the application free of your container’s dependencies.

    I hope that helps. All this stuff becomes a lot clearer when you write a server that deploys other apps within it. Most people probably never do that, but we did when we wrote our message bus (http://blog.markturansky.com/archives/26)

    Mark

  13. Brad Says:

    Hi Mark:

    How could we modify the above bootstrap program to load multiple apps? Should we run each app within a seperate thread?

    Great Article btw :D

  14. » You can’t keep a good idea down Says:

    […] deploy applications to our bus over the network by the way of a simple little bootstrap loader. You’ll note the Java class I used in my blog article uses a URLClassLoader. My example used […]

  15. Booting - Bootstrapping, Self-hosting, Self-interpreter « CK Wong’s Microscopic Universe Says:

    […] HOW TO: Bootstrap Java programs in isolated classloaders […]

Leave a Reply