This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class StaticInit1 { | |
static int SIZE = 10000000; | |
static boolean value = false; | |
public static void main(String[] args) { | |
Runnable r = new Runnable() { | |
@Override public void run() { value = true; } | |
}; | |
for (int i = 0; i < SIZE; i++) r.run(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class StaticInit2 { | |
static int SIZE = 10000000; | |
static boolean value = false; | |
static { | |
Runnable r = new Runnable() { | |
@Override public void run() { value = true; } | |
}; | |
for (int i = 0; i < SIZE; i++) r.run(); | |
} | |
public static void main(String[] args) {} | |
} |
They are almost identical, except that the former has the body in the main method while the latter has it in a static initialization block. Running the two programs produces the same result. The key difference, however, is performance: executing the first program takes about 5 ms, while executing the second takes over 40 seconds. With a performance gap of over 1000 times for two pieces of nearly identical code, this is surely worth investigating a bit.
Thanks to this blog post, I discovered two interesting flags you can pass to the JVM called -XX:+PrintCompilation and -XX:+LogCompilation which will cause it to print out all sorts of information regarding the JIT compiler, such as when methods are JIT compiled and when they are recompiled. Here is a snippet of the output for the second program (from log compilation):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<task_queued compile_id='1' compile_kind='osr' | |
method='blog/java/staticinit/StaticInit2 <clinit> ()V' bytes='58' | |
count='1' backedge_count='14563' iicount='1' osr_bci='26' stamp='0.066' | |
comment='backedge_count' hot_count='14563'/> | |
<writer thread='19228'/> | |
<nmethod compile_id='1' compile_kind='osr' compiler='C2' | |
entry='0x00000000023414a0' size='688' address='0x0000000002341350' | |
relocation_offset='296' insts_offset='336' stub_offset='464' | |
scopes_data_offset='512' scopes_pcs_offset='584' dependencies_offset='664' | |
nul_chk_table_offset='672' oops_offset='488' | |
method='blog/java/staticinit/StaticInit2 <clinit> ()V' bytes='58' | |
count='10000' backedge_count='5666' iicount='1' stamp='0.067'/> | |
<writer thread='6852'/> | |
<uncommon_trap thread='6852' reason='uninitialized' action='reinterpret' | |
compile_id='1' compile_kind='osr' compiler='C2' count='1' stamp='0.067'> | |
<jvms bci='1' method='blog/java/staticinit/StaticInit2$1 run ()V' bytes='5' | |
count='2807' backedge_count='1' iicount='15585' decompiles='1' | |
uninitialized_traps='1'/> | |
<jvms bci='27' method='blog/java/staticinit/StaticInit2 <clinit> ()V' | |
bytes='58' count='10000' backedge_count='6024' iicount='1'/> | |
</uncommon_trap> | |
<make_not_entrant thread='6852' compile_id='1' compile_kind='osr' | |
compiler='C2' stamp='0.067'/> |
I verified that in both cases the JVM tried to JIT compile the main method and the static initializer in the two examples, respectively. Furthermore, in both cases, the compiler performed the key optimization of inlining the call to run(). The discrepancy lies in the snippet, which is repeated in the log for the second program's execution many times. In short, it says that it successfully compiled the static initializer and then hit a trap because the class had not yet been initialized, which forced it to reinterpret the method rather than using the compiled version. This means that the call to run() is not inlined, leading to significantly worse performance.
Since the static initializer is executed at classloading time, it is indeed true that a class has not yet been initialized before static initialization happens. Why this means you have to reinterpret the method is unclear to me, but I'm sure the JVM writers have a good reason for it. So where does this leave us? It seems that in general static initializers cannot be JIT compiled since by definition the enclosing class has not been initialized yet. This, among other reasons, is why you should never perform expensive operations in static initializers.
Finally, I'll provide a Scala example of why this is not a completely trivial case to worry about.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object StaticInit1 { | |
val SIZE = 10000000 | |
var value = false | |
def main(args: Array[String]) { | |
for (i <- 0 until SIZE) value = true | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object StaticInit2 { | |
val SIZE = 10000000 | |
var value = false | |
for (i <- 0 until SIZE) value = true | |
def main(args: Array[String]) {} | |
} |
Scala makes it look very natural to put code into the body of an object. Unfortunately, these examples compile into bytecode that closely resembles that of the Java examples above. The for-comprehension is not implemented by a simple for loop but rather by a call to foreach, which takes a closure and calls a method on it (like the Runnable). Most importantly, the body of the second example is run in a static initializer, which causes the same problem that we discussed and the same abysmal performance.