Pages

mardi 4 juin 2013

Tweet
Recently, thanks to Kirk Pepperdine for the idea and a brainstorming session, I started working on a new open-source lock-profiler (Tyrion). Now that I have a first version working, it's time to write an article to explain the concepts for those who would want to join.

Why Tyrion ?

Because it is interresting !

Consider the following questions :

In my application, which locks are never used ?

In my application, which locks are used only by one thread ?

In my application, which locks are contended ?

To answer them, you have to dig inside the JVM and understand the inner-workings, you have to be curious, you eventually have to manipulate code that you did not write. These are challenges that sound fun !

How (high level view) ?

I wanted to instrument code to have a clear view on the critical sections of the application. Basically, I wanted to transform this kind of code (assuming bar is an instance variable) :

How (tricky technical details) ?

The bytecode of method foo looks like this.

GETFIELD #3
MONITORENTER
...
GETFIELD #3
MONITOREXIT

I want to get a reference to the target of the lock. Fortunately, the bytecode DUP is just what I need, since it duplicated the last element on the stack. I just have to transform the code so that it looks like this.

And guess what ? Using ASM, obtaining this result is really simple, as you only have to find MONITORENTER and MONITOREXIT, and add intructions before/after them. See the class LockTransformer.java for more details

Pitfall 1 - Synchronized methods

This approach is nice, but does not cover all possible cases. For instance, there is not MONITORENTER nor MONITOREXIT for synchronized methods. The JVM handles synchronization internally. The solution is to visit every synchronzied methods and insert calls to LockInterceptor at method entry and method exit.

Now the question is : what is the lock ? Simple : it is "this", and a reference to "this" in instance methods can be pushed on the stack using ALOAD_0.

So the following bytecodes should be added as the first statements of every synchronized method.

Pitfall 2 - Static methods

So far, things are cool. However, static methods are not. In a static synchronized method, the lock is the class itself, and it cannot be retrieved by ALOAD_0.

To get a reference to the class, I had to get the class FQN and insert a call to java/lang/Class.forName() instead of the ALOAD_0 instruction. So the following bytecodes should be added as the first statements of every static-synchronized method.

Conclusion & Special Thanks

That's it, synchronized blocks and methods are intercepted, and it did not required lots of code (3 bytecodes inserted at most, 5 classes to do that with ASM). The source is available on GitHub, if you want to have a look.

There are lots of subtle things going on, such as reentrant locks, but this is another topic.