In my last article, I wrote about Java agents – simple yet powerful way to instrument in runtime code running on the JVM. This article is about the same technique but using quite different tool – Java Virtual Machine Tools Interface (JVMTI). As always, my article doesn’t explain all the things about given topic – it’s valuable to me to keep some topics open so I can post an article that complements some topics. One example might be JVMTI + Golang – in this article I want to give an overview of JVMTI so when discussing it with Golang, we won’t need to focus on basics.
However, before diving deeper into JVMTI I would like to add a few facts about the topic from the previous article. I wrote there that “Compared to JVMTI they(agents written in Java) are quite limited.” What did I mean by that?
- They cannot react to events – JVM and all its goodies do lots of cool things under the hood. In order to write complex, reactive agents we need to be able to react to JVM’s behavior.
- They are being compiled to JVM bytecode, they run in JVM thus they share the JVM process with the host application. Do you know what happens with your agent when the main application throws OOM error? Your agent simply dies without the possibility to do something meaningful before.
- Similar to above, plain Java Agents are vulnerable to all slowdowns of GC.
Now, since we have a bigger picture of the feature set between JVMTI and plain Java Agents we can start by explaining why JVMTI is cool…
JVM TI is intended to provide a VM interface for the full breadth of tools that need access to VM state, including but not limited to: profiling, debugging, monitoring, thread analysis, and coverage analysis tools.
This sentence comes from the documentation and perfectly describes the capabilities of the feature in question. JVMTI agents, in theory, are quite similar to their simpler counterparts – when using them you still have to provide a special flag to the JVM start params (-agentlib:lib.so or -agentpath:/home/lib.so), they similarly have a single method that is called during startup(entry point) and so on…
The fun fact here is that they aren’t written in any language that runs on the JVM. The official documentation states that:
Agents can be written in any native language that supports C language calling conventions and C or C++ definitions.
It means that as long as you can provide a compiled library that understands calling conventions from C and knows how to handle JVMTI type definitions (provided by JVM itself) you are good to start. The JVMTI library containing JVM types is specific to a particular platform (osx, mac, win) so cross-compilation, unless using plain Java Agents, might be tricky.
Did I mention that Golang fulfills most of these requirements? Stay tuned 😉
In the demo section, I would like to show how to react to different events fired by JVM. The list of event is quite long:
This demo section is a bit special – instead of writing code myself, I will link to jvmkill repository that does a similar thing I would like to show. In my opinion, the most important lines in the whole implementation are 53 and 59. It instructs JVM to trigger the callback when
ResourceExhausted happens. That’s it! A point worth noting is that as long as we are in the callback function, our JVM is still alive so we have the possibility to execute some additional logic. That’s a very simplified demo – just take a look at the list of events & functions 🙂
Versioning & availability
It is a very important topic. Before Java 9 was introduced(11 October 2012-13 October 2016), JVMTI had a stable interface that changed very rarely. In fact, it (almost) didn’t change for over 4 years – even more, if we don’t count little modifications. Since Java 9, the JVMTI interface has a new versioning scheme – its major version contains the Java release (9, 11 and 13) and is changed with each new major Java release.
Also, remember that JVM TI may not be available in all implementations of the JavaTM virtual machine – in fact, it is available only in JVM released by Oracle (including OpenJDK).
I hope that after reading this article you understand better the pros and cons of agents on JVM. The topic about monitoring/supervising applications that run on the JVM is super interesting and hides so many interesting things under the hood that it won’t be easy to mention them all. In the future, I will be writing more about even cooler use cases so stay tuned!