Fixing Bugs in Running Java Code with Dynamic Attach

Most developers know Java’s HotSwap feature as a debugging tool that is built into most JVMs. Using this feature, it is possible to change the implementation of a Java method without restarting a Java process which is typically used via an IDE while developing code. HotSwap can however be used just as much in a production environment. Doing so, it can be used to extend a live application or to fix minor bugs in a running program without an intermediate outage. In this article, I want to showcase dynamic attachment and apply a runtime code change with the attach and instrumentation APIs and introduce Byte Buddy, a library that offers APIs for making such code changes more convenient.

As an example, consider a running application that checks for a HTTP header named X-Priority to be present in a request to undergo special treatment by the server. The check is applied by the following utility class:

class HeaderUtility {

    static boolean isPriorityCall(HttpServletRequest request) {
        return request.getHeader("X-Pirority") != null;
    }

}

Did you spot the typo? Mistakes like these are all too common, especially if constant values are factored out to static fields that are reused in test code. In an unfortunate case, this mistake would only be discovered in a production setup where the header is generated by another application without the spelling error.

Fixing a mistake like the above might not be a problem. In the age of continuous delivery, redeploying a new version might be nothing more than the click of a button. In other cases, changes might not be so easy and a redeployment might be a complex procedure where downtime is unacceptable and living with the error is a better option. With HotSwap, there is however another option for applying small changes whilst avoiding an application restart.

Attach API: Infiltrating Another JVM with Dynamic Attachment

In order to change a live Java program, we first need a way to communicate with a running JVM. As the Java virtual machine implements a managed environment, there fortunately exists a standard API for doing so. The API in question is also known as the attachment API which is part of the official Java tooling. Using this API that the running JVM exposes, it is possible for a second Java process to communicate with it.

As a matter of fact, we all have already used this API: It is applied by any debugging and monitoring tool such as VisualVM or Java Mission Control. The APIs for applying such attachments are however not bundled with the standard Java APIs that we all know and use in our day-jobs. Instead, the API is bundled in a special file, the tools.jar which is only included in a JDK-bundled distribution of the virtual machine. To make things worse, the location of this JAR file is not set, it differs on VMs for Windows, Linux and especially Macintosh where the file is not only at a different location but also named classes.jar on some distributions. Finally, IBM has decided to even rename some of the classes that are contained in this JAR by moving all com.sun classes into the com.ibm namespace, adding another hassle. In Java 9, this mess was finally cleaned up where the tools.jar is replaced by the Jigsaw module jdk.attach.

dynamic-attach

After locating the API JAR (or module) we have to make it available to the attaching process. On the OpenJDK, the class used to connect to another VM is named VirtualMachine which offers an entry point to any VM that is run by the JDK or a regular HotSpot JVM on the same physical machine. After attaching to another virtual machine process via its process id, we are able to run a JAR file in a designated thread of the targeted VM:

// the following strings must be provided by us
String processId = processId();
String jarFileName = jarFileName();
VirtualMachine virtualMachine = VirtualMachine.attach(processId);
try {
    virtualMachine.loadAgent(jarFileName, "World!");
} finally {
    virtualMachine.detach();
}

Upon receiving a JAR file, the targeted virtual machine looks up the JAR’s manifest and locates the class under the Premain-Class attribute. This is very similar to how a VM executes a main method. With a Java agent, the VM with the denoted process id does however look for a method named agentmain which is then executed by the remote process in a dedicated thread:

public class HelloWorldAgent {

    public static void agentmain(String arg) {
        System.out.println("Hello, " + arg);
    }

}

Using this API, we are now able to print a Hello, World! message on any JVM as long as we know its process id. It is even possible to communicate with JVMs that are not part of a JDK distribution as long as the attaching VM is a JDK installation in order to access the tools.jar.