How redefine a loaded class?

Gordon Lundin :

Is there a way to undefine a loaded class or overwrite it once it is loaded by a class loader?

rzwitserloot :

No, but yes.

No, the basic runtime support itself (so, java.lang.Class, and java.lang.ClassLoader) do not let you redefine classes, at all.

However, there are still 3 ways to achieve this.

  1. Containerize with classloaders. You can't redefine a class in a given classloader, but you CAN just make a new classloader, and load the class in that one.

  2. Use the debug infrastructure, which CAN do it.

  3. Buy JRebel.

Containerize

This is the usual way to do it. To use this mechanism, you need a bridge point where the code that cannot be reloaded (and at the very least, public static void main(String[] args) can never be reloaded), connects to the code that can be. The never-reloaded code can NEVER cross the bridge: If anywhere in your 'cannot be reloaded' code you mention or call any method, type, field, etc from the 'this should reload' code it will not work. The only way that you can communicate is with reflective calls. It can help if you have an interface to make your code simpler, but that does mean the interface cannot be reloaded if you do that.

Here's a basic example:

/* in the 'cannot be reloaded' side: */
ClassLoader newLoader = new MyClassLoader(RootClass.class.getClassLoader(), "/path/to/freshlyCompiled.jar");
Class<?> c = newLoader.loadClass("com.foo.ThisHasToBeAStringRef");
c.getMethod("main").invoke(null, args);

MyClassLoader is something of your own creation. classloaders have a parent, and the default implementation of loadClass, is to first ask the parent to load it (which will ask its parent, all the way to the root loader), and only then will it load it itself. The aim for you is that the classes you want to reload, are loaded by MyClassLoader (which we can replace), and NOT via the 'call to the parent' option. There are two ways to do this: Either ensure that the standard loader (so, the classpath) does NOT contain these classes, or, override loadClass and not findClass so that you can rewrite the 'ask parent first' mechanism. But note that you have to ask parent for java.lang.String and friends, or all sorts of things are going to break. An easy way is to use the already available URLClassLoader, but you MUST ensure that when you start your app, the stuff you want to reload is NOT on the classpath; it's in the jar you pass to URLClassLoader to use (and which you'll be replacing at runtime, presumably).

Various frameworks already work this way. This is how various java web frameworks offer 'hot reload', where you can just replace a war/jar file and future web requests use the new one.

NB: Any running code in the 'old' classes continue to run; this just lets you load the same class twice, really. Think of that web framework model: Any requests started before you switch continue in the old code base, any new requests are then handled by the new one.

JVMTI

The debug interface can also be used. To use this, specify an agent as you boot up the VM (it's one of the options to the java.exe command), which is run before the main app is run, and which gets an instance of the Instrumentation object. You can use it to redefine classes. The cool thing is that all existing instances will just 'jump' to the new code, unlike in the containerize strategy, but the downside is that this is an intractible / impossible problem: What if you create a new field, what value would it have? What if you change an existing field's type from 'int' to 'long'?

So, the way it works is: You cannot change any signatures. You cannot add or remove fields, rename fields, or change their types. You cannot add or remove methods, nor can you change anything about their arguments, return type, name, or exception trace. You cannot change what type(s) you extend/implement. All you can change, is the actual implementation of methods.

This is what IDEs do out of the box in debug mode (at least, eclipse does; I'm not familiar with intellij. It's likely also what Visual Code Studio does, as that runs a headless eclipse variant to run and debug java code), and they tend to call it 'hot code replace' or similar.

JRebel

JRebel is a commercial product (from an annoyingly aggressive salesteam; think twice if you want to sign up to the endless spam if you contact them) that basically rewrites all your code to turn it into virtual calls so that everything can be replaced at any time; you hot-code-replace whilst still being able to mess with signatures. The downside is, other than it being commercial of course, that the performance characteristics of code that runs in a jrebelized boot can be quite far off what normally happens.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=295366&siteId=1