How to force Java to reload class upon instantiation?

20,936

Solution 1

There's no hope of "overloading" the new operator but you could certainly write a custom class loader that simply reloads the bytecode every time you ask it to load a class. No out-of-the-box classloaders will do what you're looking for because they all assume that the class definition will not change through the life of the JVM.

But here's how you make it happen. Create a class loader called, say, Reloader which overrides the methods loadClass and findClass methods so that they simply reload the class files from disk every time they are called (instead of "caching" them for later use). Then you just have to call new Reloader().loadClass("foo.bar.MyClassName") any time you suspect the class definition has changed (e.g. as part of your testing framework's lifecycle methods).

This article fills in some of the details but misses some important points, especially about using new instances of the classloader for subsequent reloads and delegating to the default classloader when appropriate. Here is a simple working example which repeatedly loads the class MyClass and assumes its class file exists in the relative "./bin" directory:

public class Reloader extends ClassLoader {
    public static void main(String[] args) throws Exception {
        do {
            Object foo = new Reloader().loadClass("MyFoo").newInstance();
            System.out.println("LOADED: " + foo); // Overload MyFoo#toString() for effect
            System.out.println("Press <ENTER> when MyFoo.class has changed");
            System.in.read();
        } while (true);
    }

    @Override
    public Class<?> loadClass(String s) {
        return findClass(s);
    }

    @Override
    public Class<?> findClass(String s) {
        try {
            byte[] bytes = loadClassData(s);
            return defineClass(s, bytes, 0, bytes.length);
        } catch (IOException ioe) {
            try {
                return super.loadClass(s);
            } catch (ClassNotFoundException ignore) { }
            ioe.printStackTrace(System.out);
            return null;
        }
    }

    private byte[] loadClassData(String className) throws IOException {
        File f = new File("bin/" + className.replaceAll("\\.", "/") + ".class");
        int size = (int) f.length();
        byte buff[] = new byte[size];
        FileInputStream fis = new FileInputStream(f);
        DataInputStream dis = new DataInputStream(fis);
        dis.readFully(buff);
        dis.close();
        return buff;
    }
}

At each invocation of the "do/while" block in the main method, a new Reloader is instantiated which loads the class from disk and returns it to the caller. So if you overwrite the bin/MyClass.class file to contain a new implementation with a different, overloaded toString method, then you should see the new implementation each time.

Solution 2

Sounds a little scary, but this should help.

http://download.oracle.com/javase/1.4.2/docs/api/java/lang/ClassLoader.html

ClassLoader can dynamically load classes at runtime, I would read the api to determine if loading it again overrides the previous version.

Solution 3

You can give a try to Java Rebel. It is a "development-only" class-loader designed to do exactly what you need but, perhaps, it could be expensive for your needs. In your case, Peter Lawrey's solution could be enough.

Solution 4

It sounds like you want ot use the hot deployment when can be used with the debugger. When you debug a problem and recomiple some of its classes you can get the option to reload the changed classes.

EDIT: Apart from using the debugging API, you can use Instrumentation. http://download-llnw.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html

However, since using a debugger is by far the simplest way to do this, if this doesn't work, you are likely to run into the same problems.

It sounds like what you need to test smaller pieces of work so it takes less than a second run some subset of your application.

Or you could load your application faster by providing a dump and re-load facility for the memory. This way you could start your application from a snapshot (perhaps immediately)

Solution 5

I found an article on exactly this problem.
http://www.zeroturnaround.com/blog/reloading-objects-classes-classloaders/

BUT, maerics answer looks good too. will try it later.

Share:
20,936
nat101
Author by

nat101

Java Software Engineer including J2EE, JEE5, JPA, since 2001. Using Eclipse for development.

Updated on July 09, 2022

Comments

  • nat101
    nat101 almost 2 years

    Background:
    I have a MainTest class that has many buttons, each of which instantiate a class that I am coding/testing. I want the code/test cycle for these classes to be quick, and see the effect of my changes quickly, a few times a minute. MainTest which is stable takes about 20 seconds to load, which would not be a problem had I not needed to reload it for each change in the classes it instantiates. I want to load MainTest once, and when it instantiates another class, let's call it ChildTest, numerous times (upon button event), it should reload the latest version of ChildTest.

    The question in short:
    How do you tell the java 'new' command to reload the class from disk and not from jvm cache?


    I tried Class.ForName but it didn't make a difference.
    I have also tried using a custom classloader (copied from open source), to no avail.

  • nat101
    nat101 over 13 years
    I can't really use the debugger for this purpose, too slow. (extensive gui.) But my question is, if the debugger can do it, why can't I?
  • nat101
    nat101 over 13 years
    I am using Eclipse which automatically compiles upon save. The question is how to get the newly compiled class loaded into a jvm that alread has the previous version of the same class loaded.
  • nat101
    nat101 over 13 years
    I am actually working with the CustomClassLoader of that article, but am having trouble getting it to do what I want. It sounds like the right approach, but I will have to dig into the article, I guess.
  • nat101
    nat101 over 13 years
    I would need to write my own ClassLoader subclass to accomplish this.
  • Valchris
    Valchris over 13 years
    This wouldn't be very difficult to accomplish, just extend the ClassLoader and choose to always create a new instance from the source file. The API gives a brief overview of how you could do this to dynamically get a file from a network. Your case would be from a constant file source.
  • maerics
    maerics over 13 years
    You'll need to take care not to use the "new" operator for the changing class or else the default, system class loader will be used instead of your own. Try using something like this instead MyClass foo = myReloader.loadClass("MyClass").newInstance();
  • Vishy
    Vishy over 13 years
    You can if you use the debuggging API to do it. However, this is not simple to use. Changing the classloader will not change an already loaded class changing its code. see edit.
  • nat101
    nat101 over 13 years
    Yes! It does the job. Even for a gui. Thanks.
  • Erel Segal-Halevi
    Erel Segal-Halevi over 11 years
    Running the above example results in a "StackOverflow" error, unless there is a "MyFoo" class.
  • didxga
    didxga almost 11 years
    This is damnly important, "At each invocation of the "do/while" block in the main method, a new Reloader is instantiated"
  • maerics
    maerics almost 11 years
    @ErelSegalHalevi: yes, this simple example just demonstrates the concept; it doesn't claim to be robust enough for anything other than basic proof-of-concept.
  • Edoardo Vacchi
    Edoardo Vacchi over 10 years
    If anybody is interested, I have re-written a full example at this GitHub repository where the path of the class is inferred from the original (system) ClassLoader.
  • Alexander Mills
    Alexander Mills over 5 years
    When reloading a file with this CustomClassLoader, I get java.lang.SecurityException: Prohibited package name: java.lang, not exactly sure why
  • Alexander Mills
    Alexander Mills over 5 years
    Yeah I also don't know why we have to create a new instance of the ClassLoader each time? (new Reloader().loadClass()).
  • 0x52
    0x52 about 4 years
    hello, thanks for sharing your example, I don't understand why when I mock class A I don't see the change, I have open jdk13, I opened an issue in your repository.