// the java source code
public class Demo {
private final Object lock = new Object();
public void read() {
synchronized (lock) {
// more code here ...
}
}
}
// the decompiled .class file
public class Demo {
private final Object lock = new Object();
public void read() {
// Why Java compiler add this line? Is the 'read this.lock' redundant?
Object var1 = this.lock;
synchronized(this.lock) {
// more code here ...
}
}
}
The bytecode here: javap -l -p -s demo.class
public void read();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: getfield #3 // Field lock:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: aload_1
8: monitorexit
9: goto 17
12: astore_2
13: aload_1
14: monitorexit
15: aload_2
16: athrow
17: return
Exception table:
from to target type
7 9 12 any
12 15 12 any
LineNumberTable:
line 15: 0
line 16: 7
line 17: 17
LocalVariableTable:
Start Length Slot Name Signature
0 18 0 this Lxechoz/vipshop/com/demo/thread/Demo;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 12
locals = [ class xechoz/vipshop/com/demo/thread/Demo, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
I think the line 1: getfield #3 // Field lock:Ljava/lang/Object;
corresponds to Object var1 = this.lock;
.
I know the compiler will optimize the code by adding or removing some code.
But, why the compiler add a read statement before the synchronized block.
Why this is needed? Or why it is an optimization?
Here are the actual bytecodes.
public void read();
Code:
0: aload_0
1: getfield #3 // Field lock:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: aload_1
8: monitorexit
9: goto 17
12: astore_2
13: aload_1
14: monitorexit
15: aload_2
16: athrow
17: return
Exception table:
from to target type
7 9 12 any
12 15 12 any
You will see that there are two places where there aload_1
is used to load the lock from the stack frame.
(Also see @SubOptimal's pseudo-code.)
The bytecodes could be tightened up. (For example, an optimizing bytecode compiler might realize that it could reload the lock from the lock
field rather than a temporary variable. However, that is only legitimate because lock
is final
!)
However ... the Java compiler strategy is NOT to optimize the bytecodes produced by javac
. Instead, the heavy duty optimization is done at JIT compilation time. At that point, one would expect the native code to keep the lock in a register ... if that was the optimal thing to do.
The "extra variable" is probably an artifact of the decompiler that you are using. It doesn't understand the idiom that the compiler uses. It adds a local variable, without understanding that the variable is used in the synthetic handler block that it is hiding from you.
It is never wise to treat what a decompiler says as "truth". It is well known that decompiled code can be misleading ... or not even valid Java code.
Certainly any observations on code optimization that are based solely on decompiler output are without merit.
1 - Actually, if consider Thread.kill()
and the ThreadDeath
exception that is used to implement it, it is not hypothetical. Even for an empty block.