For analyzing the runtime behavior of applications, or for implementing plug-ins, is there any way to add hooks to methods' input, output, etc. inside the running application?
The idea:
Collection.class.getHooks().add("size", Event.BEGIN, (args, vars) -> {
System.out.println("Collection.size() called with args: " + args);
vars.put("start", System.nanoTime());
});
Collection.class.getHooks().add("size", Event.END, (returnValue, vars) -> {
System.out.println("Collection.size() returned: " + returnValue);
Long start = (Long) vars.get("start");
System.out.println("Collection.size() took " + (System.nanoTime() - start) + " ns");
});
Collection<String> c = new ArrayList<>();
c.add("foo");
c.size();
Should print something like:
Collection.size() called with args: null
Collection.size() returned: 1
Collection.size() took 123 ns
You have 4 options. None of them are anywhere near as 'simple' as what you wrote. That's because what you wrote requires a virtual machine that is at its core a monkeypatch party: All lookups are always redirected to a program-modifying hashmap-esque key/value map, where methods are themselves possible values. Languages like Python and JavaScript are like that. Java is not: That kind of 'anything goes' is detrimental to maintainability (it turns all types into global mutable state.
I leave it as an exercise to the reader to do some reading on what global mutable state is, and finding the very many blogposts and articles that demonize it. I mostly happen to agree with that stance - they are indeed tricky things that tend to lead to unmaintainable code, but we're delving into opinion which is beyond the bailiwick of Stack Overflow).
Option one is a Proxy, but [A] this requires an interface, and [B] Requires modifying the code at the point where the object is created, or at least modifying the code you want to 'measure' by replacing the instance with the proxy wrapper.
Probably not what you want.
But if it is, just find any tutorial on how to use java.lang.reflect.Proxy - start with the javadoc (linked).
Agents are debug tools. The functionality they offer:
This means you can use them to 'rewrite' the bytecode of anything being loaded, though, as an agent is itself java code, it can be a bit tricky to rewrite java.*
core classes. It should still be possible (the agent gets its own tiny little classloader independent of the 'main' app for just this purpose), but if all you care about is not-core classes invoking core classes methods, you can instead just check all bytecode for e.g. INVOKEINTERFACE java.util.Collection size()I
and inject a call to your logging thing beforehand, or replace the call to a static method that serves the purpose of 'wrapping' the call.
To do this, make an agent (these are jars with instead of in the manifest Main-Class
, an Premain-Class
or Agent-Class
entry. A good place to start reading up on how this works is the javadoc of the java.lang.instrument
package.
AOP stands for Aspect Oriented Programming.
Look into tools like Spring AOP, but we're back to requiring you to include this as part of your build. It's all about this sort of thing, really.
A fourth option is the Java Flight Recorder. It's designed for monitoring only, but your example code appears to want to do just that.
What you're doing is microbenchmarking which does not work. Computers and JVMs are way, way more complicated than 'I can just nanotime all the things and the numbers that roll out will have some meaning'. They mostly don't, and trying to do performance measurements in this way is going to lead to gobbledygook: You get numbers. They won't mean what you think they mean. So don't do it.
If you must microbenchmark, you need to write code that is explicitly designed to do that, and you need to use JMH.
Happy reading!