Monthly Archives: June 2010

Bytecode Analysis of ArrayBlockingQueue

As part of my personal code kata, I’ve been studying the code of the ArrayBlockingQueue, originally written by Doug Lea. It is a great example of writing concurrent code. For example, the method for taking something out of the queue, while waiting until there actually is something in the queue, looks like this:


public E take() throws  InterruptedException {
    final  ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        try {
            while (count == 0)
            notEmpty.await();
        } catch (InterruptedException ie) {
            notEmpty.signal(); // propagate to non-interrupted thread
            throw ie;
        }
        E x = extract();
        return x;
    } finally {
        lock.unlock();
    }
}

This is seen as the prototypical example of how to use the Java Lock class, so it is interesting enough from that perspective. Many people have written about that bit, so I will not attempt to do a better job. One thing I was unable to figure out though is the first line of take(), in which the field lock is assigned to the local variable (also called lock). Intuitively, it would seem like this line would be necessary if the field is volatile and the method needs a reliable copy. However, the lock field in ArrayBlockingQueue is final, so volatility is not a problem. I found an entry on Stack Overflow which gave a teaser, saying the byte code is more compact and efficient. I did not find this answer sufficient, so I did my own little study to dig deeper into why this makes the byte code more compact.

I copied the methods take and put and any needed supporting fields and method, and decompiled the class to bytecode (using javap -c). In the first compilation, I left the code as is, but in the second I removed the assignment to a local variable:

Original:

public  java.lang.Object take()   throws java.lang.InterruptedException;
Code:
0:   aload_0
1:   getfield        #34; //Field  lock:Ljava/util/concurrent/locks/ReentrantLock;
4:   astore_1
5:   aload_1
6:   invokevirtual   #78; //Method  java/util/concurrent/locks/ReentrantLock.lockInterruptibly:()V
9:   goto    21
12:  aload_0
13:  getfield        #40;  //Field notEmpty:Ljava/util/concurrent/locks/Condition;
16:   invokeinterface #81,  1; //InterfaceMethod  java/util/concurrent/locks/Condition.await:()V
21:  aload_0
22:  getfield        #64; //Field count:I
25:  ifeq    12
28:  goto    43
31:  astore_2
32:  aload_0
33:   getfield        #40; //Field  notEmpty:Ljava/util/concurrent/locks/Condition;
36:   invokeinterface #66,  1; //InterfaceMethod  java/util/concurrent/locks/Condition.signal:()V
41:  aload_2
42:  athrow
43:  aload_0
44:  invokespecial   #84; //Method  extract:()Ljava/lang/Object;
47:  astore_2
48:  aload_2
49:  astore  4
51:  aload_1
52:  invokevirtual   #86;  //Method java/util/concurrent/locks/ReentrantLock.unlock:()V
55:   aload   4
57:  areturn
58:  astore_3
59:  aload_1
60:  invokevirtual   #86; //Method  java/util/concurrent/locks/ReentrantLock.unlock:()V
63:  aload_3
64:  athrow
Exception table:
from   to  target type
9    28    31   Class java/lang/InterruptedException

9     51    58   any

Altered:

public java.lang.Object  take()   throws java.lang.InterruptedException;
Code:
0:    aload_0
1:   getfield        #34; //Field  lock:Ljava/util/concurrent/locks/ReentrantLock;
4:    invokevirtual   #78; //Method  java/util/concurrent/locks/ReentrantLock.lockInterruptibly:()V
7:   goto    19
10:  aload_0
11:  getfield        #40;  //Field notEmpty:Ljava/util/concurrent/locks/Condition;
14:   invokeinterface #81,  1; //InterfaceMethod  java/util/concurrent/locks/Condition.await:()V
19:  aload_0
20:  getfield        #64; //Field count:I
23:  ifeq    10
26:  goto    41
29:  astore_1
30:  aload_0
31:   getfield        #40; //Field  notEmpty:Ljava/util/concurrent/locks/Condition;
34:   invokeinterface #66,  1; //InterfaceMethod  java/util/concurrent/locks/Condition.signal:()V
39:  aload_1
40:  athrow
41:  aload_0
42:  invokespecial   #84; //Method  extract:()Ljava/lang/Object;
45:  astore_1
46:  aload_1
47:  astore_3
48:  aload_0
49:  getfield        #34;  //Field lock:Ljava/util/concurrent/locks/ReentrantLock;
52:   invokevirtual   #86; //Method  java/util/concurrent/locks/ReentrantLock.unlock:()V
55:  aload_3
56:  areturn
57:  astore_2
58:  aload_0
59:   getfield        #34; //Field  lock:Ljava/util/concurrent/locks/ReentrantLock;
62:   invokevirtual   #86; //Method  java/util/concurrent/locks/ReentrantLock.unlock:()V
65:  aload_2
66:  athrow
Exception table:
from   to  target type
7    26    29   Class java/lang/InterruptedException

7     48    57   any

You will notice that in the first snippet, the comment “Field lock:Ljava/util/concurrent/locks/Reentrant” appears once, but the same comment, indicating a field access, appears twice in the second snippet. The reason the byte code is longer in the altered version is because every time a field is accessed, the owning object must be pushed on the JVM’s operand stack, and then the getfield opcode pops the object and pushes the field onto the stack (the overall process takes two opcodes). However, when a local variable is accessed, it is put on the stack with a single opcode, which makes the byte code smaller and faster. This is an optimization that should rarely be used, however.