In brief: We've open sourced a tool that allows you to provide a callback every time your program performs an allocation. The Java Allocation Instrumenter can be found here. Give it a whirl, if you are interested.
One thing that crops up a lot at my employer is the need to take an action on every allocation. This can happen in a lot of different contexts:
Because of the demand for this, a few of us put together a tool that instruments your code and invokes a callback on every allocation. The Allocation Instrumenter is a Java agent written using the java.lang.instrument API and ASM. Each allocation in your Java program is instrumented; a user-defined callback is invoked on each allocation.
The easiest way to explain this is with an example. Assume you have a program that creates 10 strings, and you want to instrument it:
You can then compile and run the program:
The output will look something like this:
So, by my standards, it is really pretty easy to use. If you find it useful, please let me know!
Edited to add I noticed this on Twitter: Cool, even if it uses Ant (so probably I will never try it). This is funny, because I only added an ant buildfile so more people would try it. You can download the source and compile it with javac in about one line.
- The programmer has a task, and wants to know how much memory the task allocates, so wants to increment a counter on every allocation.
- The programmer wants to keep a histogram of most frequently accessed call sites.
- The programmer wants to prevent a task from allocating too much memory, so it keeps a counter on every allocation and throws an exception when the counter reaches a certain value.
Because of the demand for this, a few of us put together a tool that instruments your code and invokes a callback on every allocation. The Allocation Instrumenter is a Java agent written using the java.lang.instrument API and ASM. Each allocation in your Java program is instrumented; a user-defined callback is invoked on each allocation.
The easiest way to explain this is with an example. Assume you have a program that creates 10 strings, and you want to instrument it:
public class Test {
public static void main(String [] args) throws Exception {
for (int i = 0 ; i < 10; i++) {
new String("foo");
}
}
}
To do this, you create an instance of the interface Sampler:
import com.google.monitoring.runtime.instrumentation.AllocationRecorder;
import com.google.monitoring.runtime.instrumentation.Sampler;
public class Test {
public static void main(String [] args) throws Exception {
AllocationRecorder.addSampler(new Sampler() {
public void sampleAllocation(int count, String desc,
Object newObj, long size) {
System.out.println("I just allocated the object " + newObj +
" of type " + desc + " whose size is " + size);
if (count != -1) { System.out.println("It's an array of size " + count); }
}
});
for (int i = 0 ; i < 10; i++) {
new String("foo");
}
}
}
You can then compile and run the program:
% javac -classpath path/to/allocation.jar Test.java
% java -javaagent:path/to/allocation.jar Test
The output will look something like this:
I just allocated the object foo of type java/lang/String whose size is 24
I just allocated the object foo of type java/lang/String whose size is 24
I just allocated the object foo of type java/lang/String whose size is 24
I just allocated the object foo of type java/lang/String whose size is 24
I just allocated the object foo of type java/lang/String whose size is 24
I just allocated the object foo of type java/lang/String whose size is 24
I just allocated the object foo of type java/lang/String whose size is 24
I just allocated the object foo of type java/lang/String whose size is 24
I just allocated the object foo of type java/lang/String whose size is 24
I just allocated the object foo of type java/lang/String whose size is 24
So, by my standards, it is really pretty easy to use. If you find it useful, please let me know!
Edited to add I noticed this on Twitter: Cool, even if it uses Ant (so probably I will never try it). This is funny, because I only added an ant buildfile so more people would try it. You can download the source and compile it with javac in about one line.
Comments
Patrick
The three examples at the top of the post are the three main use cases I have found for this code. It can be very useful, for example, to have a histogram of call sites where the bulk of your allocation takes place. (We tend to sample the allocation call sites rather than track every one, because getting a stack trace at every allocation is very heavyweight).
DTrace actually has hooks that allow you to instrument allocations in a similar way, but the hooks have to be written in a DTrace-friendly way (i.e., not in Java). BTrace could hook into the DTrace hooks without too many problems. I recall a VM patch that was floating around a few months ago to enable it, but I don't know what became of it.
For example if I test with your test class with different sizes of strings the reported object size is always 40 for all the strings.
Was the performance of getting the object size from the instrumentation instance so poor that it necessitated this cache?
@Jaroslav - You're completely right! I had no idea. Since it looks as if you are a contributor - does it plug into the DTrace VM hooks, or does it do a similar bytecode rewrite?
By default BTrace uses bytecode instrumentation. But you can use it to hook into the DTrace machinery as well (see DTraceInline.java and DTraceDemoRef.java)
Thanks
Suraj
1) If you want to defer the cost of constructing a stack trace, constructing a new Throwable means that you can defer the expensive creation of the stack trace until you need it. (Try creating a bunch of Throwables and comparing that cost to the creation of a bunch of stack traces)
2) You might secretly not need all of the stack traces. One trick is to gather a statistically valid sample. We've found that a decent sample is to grab one every time a thread allocates ~512K of objects, where ~ means a statistically valid sampling distributed around the number 512K.
3) It depends on how much allocation you do, of course. Simply instrumenting the code can cost anywhere from 5-10% to 50%, depending on the application.
Roberto
I am not sure if this is a bug or I am misinterpreting the output of the sampler, I have this code:
public class HelloWorld{
public HelloWorld() {
String s11 = new String("constructor");
}
public static void main(String args[]){
Object o1 = new Object();
String s1 = new String("hello");
System.out.println("Example");
Object o2 = new Object();
}
}
and it seems to me that the object allocated in the constructor is not being detected. I expect to see something like "I just allocated the object constructor of type java/lang/String whose size is ..." in the output, but this is not the case.
What is going on?
public class HelloWorld{
public HelloWorld() {
String s11 = new String("constructor");
}
public static void main(String args[]){
AllocationRecorder.addSampler(new Sampler() {
public void sampleAllocation(int count, String desc,
Object newObj, long size) {
System.out.println("I just allocated the object " + newObj +
" of type " + desc + " whose size is " + size);
if (count != -1) { System.out.println("It's an array of size " + count); }
}
});
Object o1 = new Object();
String s1 = new String("hello");
System.out.println("Example");
Object o2 = new Object();
}
}
public class Wrapper {
public static void main(String[] args) {
AllocationRecorder.addSampler(new Sampler() {
public void sampleAllocation(int count, String desc, Object newObj, long size) {
// whatever
}
});
OriginalProgram.main(args);
}
}
We could add a command line loading facility, but this is easier for me :)