May 12, 2015

In this post we’ll look at two approaches to obtaining the bytecode of classes loaded into the JVM and learn a thing or two about how to write javaagents and use HotSpot Debugger, a hidden gem of the JDK. But why would you need to do this? Let's look at a few scenarios.

Imagine a situation where you suddenly need to obtain the bytecode of all the loaded classes in a Java process. For example let’s say you want to debug some sort of instrumentation that happens at runtime and you can’t locate the .class file and inspect that. You need to dig deeper.

Alternatively, let’s say you’re doing something along the lines of JITWatch and want to link together the bytecode you produce with the machine code the JIT generates.

In any case, you have a problem which involves classes and as we know all too well, problems with classes and classloaders are gonna get interesting and you’re gonna need coffee. Lots of coffee! So let’s outline the problem that we’re tackling in this post.

Problem Description: Obtain the bytecode of all classes loaded into the JVM.

Let's get started!

Writing a javaagent

In general, a javaagent is a JVM “plugin”, a specially crafted .jar file, that utilizes the Instrumentation API that the JVM provides. The Instrumentation API was made available in Java 1.5, so for all intents and purposes, the solution we’re going to explore now is quite portable.

To create a successful javaagent we’ll need four things: an agent class, some meta-information to tell the JVM what capabilities to give to our agent class, a way to make the JVM load the .jar with the agent before it starts minding the application’s business and a coffee. Got the coffee already? OK, three things then.

Above is a slide from my talk: Taming Java Agents, which I most recently presented to the Barcelona JUG meeting a couple of weeks ago.

I have to be honest here, originally, the slide was created by Java Champion, JavaOne Rockstar Speaker, and XRebel product lead Anton Arhipov , so all credit should go to him. However *looks around* he’s not here right now so I’ll claim it as mine!

Creating a Java Class

So without further ado, let’s create a Java class that will be our agent class. The important thing to remember here is that our VM will try to locate our class that we specify in our -javaagent parameter to the VM, and execute its premain method before the main method.

import java.lang.instrument.Instrumentation; public class Agent { public static void premain(String args, Instrumentation instrumentation){ ClassLogger transformer = new ClassLogger(); instrumentation.addTransformer(transformer); } }



Notice the Instrumentation parameter that we have access to in the premain method. It is a very powerful API that among other things allows us to register ClassFileTransformers . A registered ClassFileTransformer will intercept the loading of all application classes and will have access to their bytecode.

Incidentally, ClassFileTransformer can also transform the bytecode of the application classes and make the JVM load behave completely differently to the originally intended bytes.

That’s how almost all cool JVM tools work, including JRebel and XRebel, they intrument your classes and inject their functionality right into your app without requiring additional dependencies or even changing your code. By the way, while we’re on the subject, look into those two tools, they are truly fascinating and if you are a Java developer and don’t know about JRebel or XRebel yet, you’ll love them!

However, we digress, let’s get back to logging the bytecode! So, we have now registered a ClassLogger transformer, let’s look how it is implemented.

Implementing a ClassLogger Transformer

public class ClassLogger implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { try { Path path = Paths.get("classes/" + className + ".class"); Files.write(path, classfileBuffer); } catch (Throwable ignored) { // ignored, don’t do this at home kids } finally { return classfileBuffer; } } }



As you can see the code is extremely trivial here. The transform method has access to the application class name and the bytes that correspond to the body of the class. In our case we just dump the bytes into a file.

Sadly, that’s it, the coding time is over, the last part that needs to be done is packaging a jar file and supplying a manifest file that will specify our agent as the Premain-Class . Here’s a Gradle build file part that would do a trick:

jar { archiveName = "${rootProject.name}-${rootProject.version}.jar" manifest { attributes( 'Premain-Class': 'Agent', 'Can-Redefine-Classes': 'true', 'Can-Retransform-Classes': 'true', 'Can-Set-Native-Method-Prefix': 'true', 'Implementation-Title': "ClassLogger", 'Implementation-Version': rootProject.version ) } }

Building a javaagent jar

All the bits and pieces are ready for you to build your javaagent jar now and all you have to do to enjoy the power of intercepting classloading to log the bytes of the classes is to supply the -javaagent parameter. It’s as simple as this:

java -jar myapp.jar java -javaagent:/path/to/agent.jar -jar myapp.jar



And when the myapp.jar is done running, under the classes directory you’ll find a bunch of .class files to inspect. I don’t have the source code for the agent at hand, but in this convenient Github repository you can find a complete javaagent project that is a bit more complicated, but follows the same principles. Check it out, it’s pretty cool.

Using HSDB to Check Bytecode

Earlier, we looked at how to create a simple javaagent that would intercept the classloading and write the bytes of all loaded classes into files on our file system. That would be my default way of solving the problem of obtaining the bytecode for all loaded classes. In fact javaagents are particularly powerful, in fact the defacto way to tackle many similar problems as well.

But now I want to share another way of checking the bytecode of classes and do other cool things with a tool called HSDB (HotSpot Debugger). I’ve learned about it at the recent SpringIO conference in Barcelona, thanks to Thomas and am excited to share it with you.

HSDB resides in a sa-jdi.jar that you’ll find under the lib directory of your local HotSpot JDK distribution.

To unleash the power of HSDB onto the Java process of your choosing, you’ll have to run it with privileged access and feed it the PID of the Java process that you want to investigate:



From the main menu, select File => Attach to HotSpot process … and enter the PID of your Java process. By the way, one of the best ways to obtain the PID of a Java process is by using jps, another built-in JDK tool.

Straight after connecting, we can see a list of Threads in the target Java process (I picked Eclipse as my Java process).

And that is a great thing to look into, if you have some free time, but something more relevant to our problem of getting the class files view would be available through Tools => Class browser.

You can see the Create .class for all classes link right at the top of this new window. Clicking that will generate all the loaded application class files and we’ve solved the same problem much faster, though maybe in a less satisfying geek way than our javaagent route. The HotSpot Debugger functionality is of course not limited to generating class files. It’s an incredibly powerful tool, but the rest is a story for another time. At the beginning of this post, we set ourselves on the path of obtaining the class files and we achieved just that. You’re welcome bytecode loving friends!

Final Thoughts

In this post we’ve looked at two super-mega-powerful entries in the Java developer toolbelt: the javaagent and the HotSpot Debugger. Both of them are very versatile and with the power to access to classes loaded into the JVM, we have only just scratched the surface of what can achieved with them, but hopefully after reading this post you’ll now know of their existence and can investigate things further. Let me know how you got on! What would be your way of solving the problem at hand? Tell me on Twitter: @shelajev.

Additional Resources

Want in-depth discussion on the big changes to Java as a language? This webinar from our Java experts is can't-miss.

If you’re just getting acquainted with bytecode, here’s a helpful report to get you started: Mastering Java Bytecode.

Improve Performance During Java Development

Did you know JRebel has a sister product called XRebel?

XRebel helps developers to find and fix performance issues during application development, leading to better performing applications and happier clients.

Try it free on your project with a 10 day trial, here.

Try XRebel Free