3

I was hoping for someone to explain this item since I might be getting this wrong:

I was reading about Java Agent Instrumentation which says that the agent can start after VM startup. So if I want to dynamically replace some class (without brining down the app) is this what I am going to go for using agent-main? Or do I need to do something more here?

I know people might ask "Are you talking about JRebel" - not really because I want to do something simple and JRebel is an overkill.

instrument docs - Java docs for Instrumentation

I understand all the instrumentation overrides, but I am slightly confused how I can hook this agent with -agent argument after the app has started.

Zabuzard
  • 25,064
  • 8
  • 58
  • 82
ha9u63a7
  • 6,233
  • 16
  • 73
  • 108
  • Note that if you have access to the targeted applications code then there are more easy ways to load code dynamically. Indeed Javas design makes it very easy to just compile and load some classes at runtime, you do not need an agent for that (just google for "*java dynamically load class*"), it involves using `ClassLoader`. But if you have no access to the targets code then this may be a good way to inject your agent. – Zabuzard Aug 15 '17 at 22:45
  • @Zabuza are you talking about dynamic loading without restarting application? Because that's what I want to do. In a production system you never (well, should not) have any source code present so using agent is probably the most preferred way if anything – ha9u63a7 Aug 15 '17 at 22:47
  • An agent allows you to inject code into other processes, without changing stuff in the targets code. But if you are inside your project and want to later load stuff, then you can just use `ClassLoader.loadClass(...)` at that point. It enables you to load classes dynamically at runtime, without restarting the project. The advantage is that the later does not need any fiddling around with libraries, it is there by default and very easy to use. – Zabuzard Aug 15 '17 at 22:49
  • @Zabuza no all I want is just replace a particular class with an updated one - the idea is that classloader has already loaded the class, but if there is a code change I would like to deploy the changed class without actually restarting the entire application. It seems like agent is the choice. – ha9u63a7 Aug 15 '17 at 22:54
  • Ah, reloading a class can also be made and it does not involve the usage of agents but also `ClassLoader`s. Let me write another answer. – Zabuzard Aug 15 '17 at 23:06
  • @Zabuza yes but isn't that supposed to attach to the target JVM and then do stuff? – ha9u63a7 Aug 15 '17 at 23:07

3 Answers3

3

First your agent class needs to specify an agentmain method like:

public class MyAgent {
    public static void agentmain(final String args, final Instrumentation inst) {
        try {
            System.out.println("Agent loaded.");
        } catch (Exception e) {
            // Catch and handle every exception as they would
            // otherwise be ignored in an agentmain method
            e.printStackTrace();
        }
    }
}

Compile it and pack it inside a jar-file for example. If you choose the jar-variant then it must specify the Agent-Class key in its manifest-file (MANIFEST.MF). It points to the class implementing the agentmain method. It could look like:

Manifest-Version: 1.0
Agent-Class: package1.package2.MyAgent

If it is located inside those packages, as an example.


After that you can load the agent via the VirtualMachine#loadAgent method (documentation). Note that the mechanism used by those classes are part of the Attach library of Java. They decided, as most users don't need it, to not directly add it to the systems path but you can just add it. It is located at

pathToYourJDKInstallation\jre\bin\attach.dll

And it needs to be somewhere where the system property java.library.path is pointing at. You could for example just copy it to your .../Windows/System32 folder or adjust the property or stuff like that.


As an example, if you want to inject an agent-jar inside another currently running jar, you could use a method like this:

public void injectJarIntoJar(final String processIdOfTargetJar,
        final String pathToAgentJar, final String[] argumentsToPass) {
    try {
        final VirtualMachine vm = VirtualMachine.attach(processIdOfTargetJar);
        vm.loadAgent(pathToAgentJar, argumentsToPass.toString());
        vm.detach();
    } catch (AttachNotSupportedException | AgentLoadException
            | AgentInitializationException | IOException e) {
        System.err.println("Unable to inject jar into target jar.");
    }
}

With the same technique you can inject dll-libraries (if they implement the corresponding agent-methods via the native agent interface) into jars.


Actually, if that helps you, I have written some small library for that kind of stuff some time ago. See Mem-Eater-Bug, the corresponding class is Injector.java and the whole project has a small Wiki.

It has an example showing how to use that technique to manipulate a SpaceInvaders game written as Java application.

Zabuzard
  • 25,064
  • 8
  • 58
  • 82
  • As a note I'd like to add a small overview: There is one jar, the **target**, which is currently running. Then you have an **againt-jar** that you want to inject into the target. And finally you have an application that injects the agent into the target, the **injector**. The injector will have the method described above. After it was executed the **agent** gets injected into the **target** and its **agentmain** will be executed. At this point both share the same heap, so you can manipulate stuff from the **target** from inside the **agent**. – Zabuzard Aug 15 '17 at 22:55
  • 1
    yes this is what I did, but I was just curious to know whether any additional settings need to be done for starting after VM. Seems like all is good here :) – ha9u63a7 Sep 11 '17 at 08:50
2

So apparently you want to reload classes at runtime. Such that your project can react to changes of the code without restarting.

To achieve this you need to prepare your project and write a very clean architecture, it involves using interfaces, factory-patterns, proxy-patterns and a routine that checks for updates and then destroys and rebuilds all current objects.

Unfortunately this might not be an easy task, but it is doable, depending on the size of your project and the amount of code that should react dynamically to changes.


I found this article helpful, let me explain how it works. You can easily load a class with ClassLoader.loadClass(...) and you can also use that to reload a class, very easy. However at the time you have compiled your code classes are some kind of hardwired already. So your old code will continue to create instances of the old classes although you have reloaded the class.

This is the reason why we need some kind of architecture that allows exchanging the old class with the new class. Also it is pretty obvious that current instances of the old class can not automatically be transferred to the new version as everything could have changed. So you will also need a custom method that collects and rebuilds those instances.


The approach described in the article uses an Interface instead of an actual class in the first place. This allows to easily exchange the class behind that interface without breaking the code that uses the interface.

Then you need a factory where you ask for instances of that Interface. The factory can now check if the underlying class-file has changed, if so it reloads it and obtains a reference to the new class version. It can now always create an instance of the interface which uses the up-to-date class.

The factory, by that, is also able to collect all created instances in order to exchange them later, if the code base has changed. But the factory should reference them using WeakReference (documentation), else you have a big memory leak because the Garbage Collector would not be able to delete instances because the factory holds references to them.


Okay, now we are able to always obtain up-to-date implementations of an Interface. But how can we easily exchange existing instances. The answer is by using a proxy-pattern (explanation).

It is simple, you have a proxy class which is the actual object you are working with. It has all the methods of the Interface and upon calling methods it simply forwards to the real class.

Your factory, as it has a list of all current instances using WeakReference, can now iterate the list of proxies and exchange their real class with a new up-to-date version of the object.

Existing proxies that are used all around your project will now automatically use the new real version as the proxy itself has not changed, only its internal reference to the real target has changed.


Now some sample code to give you a rough idea.

The interface for the objects you want to monitor:

public interface IExample {
    void example();
}

The real class, which you want to rebuild:

public class RealExample implements IExample {
    @Override
    public void example() {
        System.out.println("Hi there.");
    }
}

The proxy class that you will actually use:

public class ProxyExample implements IExample {
    private IExample mTarget;

    public ProxyExample(final IExample target) {
        this.mTarget = target;
    }

    @Override
    public void example() {
        // Forward to the real implementation
        this.mRealExample.example();
    }

    public void exchangeTarget(final IExample target) {
        this.mTarget = target;
    }
}

The factory you will mainly use:

public class ExampleFactory {
    private static final String CLASS_NAME_TO_MONITOR = "somePackage.RealExample";
    private final List<WeakReference<ProxyExample>> mInstances;
    private final URLClassLoader mClassLoader;

    public ExampleFactory() {
        mInstances = new LinkedList<>();

        // Classloader that will always load the up-to-date version of the class to monitor
        mClassLoader = new URLClassLoader(new URL[] {getClassPath()}) {
            public Class loadClass(final String name) {
                if (CLASS_NAME_TO_MONITOR.equals(name)) {
                    return findClass(name);
                }

                return super.loadClass(name);
            }
        };
    }

    private IExample createRealInstance() {
        return (IExample) this.mClassLoader.loadClass(CLASS_NAME_TO_MONITOR).newInstance();
    }

    public IExample createInstance() {
        // Create an up-to-date instance
        final IExample instance = createRealInstance();

        // Create a proxy around it
        final ProxyExample proxy = new ProxyExample(instance);
        // Add the proxy to the monitor
        this.mInstances.add(proxy);

        return proxy;
    }

    public void updateAllInstances() {
        // Iterate the proxies and update their references
        // Use a ListIterator to easily remove instances that have been cleared
        final ListIterator<WeakReference<ProxyExample>> instanceIter =
            this.mInstances.listIterator();
        while (instanceIter.hasNext()) {
            final WeakReference<ProxyExample> reference = instanceIter.next();
            final ProxyExample proxy = reference.get();

            // Remove the instance if it was already cleared,
            // for example by the garbage collector
            if (proxy == null) {
                instanceIter.remove();
                continue;
            }

            // Create an up-to-date instance for exchange
            final IExample instance = createRealInstance();
            // Update the target of the proxy instance
            proxy.exchangeTarget(instance);
        }
    }
}

And finally how to use it:

public static void main(final String[] args) {
    final ExampleFactory factory = new ExampleFactory();

    // Get some instances using the factory
    final IExample example1 = factory.createInstance();
    final IExample example2 = factory.createInstance();

    // Prints "Hi there."
    example1.example();

    // Update all instances
    factory.updateAllInstances();

    // Prints whatever the class now contains
    example1.example();
}
Zabuzard
  • 25,064
  • 8
  • 58
  • 82
  • Please note that I have not tested the code, but it should give you a good idea how to do it. – Zabuzard Aug 16 '17 at 00:06
  • 1
    Very nice answers, this is the kind of content this site should have. – Oleg Aug 16 '17 at 00:23
  • 1
    @Zabuza thanks for your answer. Since writing a totally new application using factory pattern isn't practical - I have gone for the agent approach and it works. The only drawback I can see is that the agent is never released by the application even after the `agentmain()` has executed successfully. – ha9u63a7 Sep 11 '17 at 08:49
  • Glad to hear that it helped. Note that unloading seems to be possible, but limited as shown here: [Unloading a JVMTI agent at runtime?](https://stackoverflow.com/questions/6124855/unloading-a-jvmti-agent-at-runtime) – Zabuzard Sep 11 '17 at 12:14
2

Attaching an agent at runtime requires use of the attach API which is contained in the tools.jar until Java 8 and is contained in its own module starting from Java 9. The location of the tools.jar and the name of its classes is system-dependent (OS, version, vendor) and as of Java 9 it does not exist at all but must be resolved via its module.

If you are looking for an easy way to access this functionality, try out Byte Buddy which has a subproject byte-buddy-agent for this. Create a Java agent as you are used to it but add anAgent-Main entry where you would put your Pre-Main in the manifest. Also, name the entry method agentmain, not premain.

Using byte-buddy-agent, you can write a program:

class AgentLoader {
  public static void main(String[] args) {
    String processId = ...
    File agentJar = ...
    ByteBuddyAgent.attach(processId, agentJar);
  }
}

and you are done.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • Thank you for posting you answer, I personally learned about a new library which might be useful in the future but haven't you forgot about something starting with a D? – Oleg Aug 17 '17 at 14:43
  • D like in disclaimer? Did not find it necessary here. – Rafael Winterhalter Aug 17 '17 at 14:54
  • Your answer feels somewhat like an ad to me. I wouldn't say it's necessary but I think it's good form. Do as you see fit, I'm not going to flag or anything. – Oleg Aug 17 '17 at 15:06
  • Well, I am the author of Byte Buddy, an open source tool, this is documented herewith ;) – Rafael Winterhalter Aug 17 '17 at 16:32