Java Secret: Are static blocks interpreted?

Overview

An interesting comment was raised by @Vyadh.

Are static block interpreted or does the JIT play a part?

When are methods optimised?

A method will be optimised when it is called often enough. This is controlled by the -XX:CompileThreshold=10000 flag. The compilation occurs in the background by default and a short time later, the optimised version of the code will be used. (Which is why it doesn't always happen at exact the 10K mark)

However, loops can also be optimised if they are called often enough. This typically takes about 2-5 seconds. This is why any bound loop which does nothing takes about 2-5 seconds. (Which is how long it takes the JIT to realise the loop doesn't do anything)

How does this apply to static blocks?

static block are never called more than once. Even if they are loaded by different class loaders, they are optimised independently. (In the unlikely event you loaded the same class 10,000 times, it still wouldn't be optimised)

However, it is quite possible to have a loop iterate many times in a static block (though rare) This can result in the static block begin optimised by the JIT.

public class Main {
    static {
        long start = System.nanoTime();
        for (int i = 0; i < 5000; i++) ;
        long time = System.nanoTime() - start;
        System.out.printf("Took %.3f ms to iterate 5 thousand times%n", time / 1e6);

        long start1 = System.nanoTime();
        for (int i = 0; i < 5000; i++) ;
        long time1 = System.nanoTime() - start1;
        System.out.printf("Took %.3f ms to iterate 5 thousand times%n", time1 / 1e6);

        long start2 = System.nanoTime();
        for (int j = 0; j < 1000 * 1000; j++)
            for (int i = 0; i < 1000 * 1000; i++) ;
        long time2 = System.nanoTime() - start2;
        System.out.printf("Took %.3f ms to iterate 1 trillion times%n", time2 / 1e6);

        long start3 = System.nanoTime();
        for (int j = 0; j < 1000 * 1000; j++)
            for (int i = 0; i < 1000 * 1000; i++) ;
        long time3 = System.nanoTime() - start3;
        System.out.printf("Took %.3f ms to iterate 1 trillion times%n", time3 / 1e6);
    }

    public static void main(String[] args) {
    }
}
run the with -XX:+PrintCompilation flag outputs.
64   1       java.lang.String::charAt (33 bytes)
     75   2       java.lang.String::hashCode (64 bytes)
Took 0.067 ms to iterate 5 thousand times
Took 0.062 ms to iterate 5 thousand times
     88   1%      Main:: @ 124 (249 bytes)
Took 4.860 ms to iterate 1 trillion times
Took 0.001 ms to iterate 1 trillion times

You can see that a loop of 5,000 is not enough to trigger optimisation, but a much larger loop does trigger optimisation. Without the JIT, there is no way the loop of one trillion time would finish in 3 seconds.

Once the method has been optimise the last one trillion loop takes practically no time at all.

Note

@Vyadh notes that the client JVM doesn't appear to perform this optimisation and will wait a very long time.

Comments

  1. Hi Peter
    I've enjoyed reading your blog posts. Would you consider joining DZone's MVB program (dzone.com/aboutmvb)? I'm an editor over at DZone, and think that you would be a great addition to the program.
    If you're interested, send me an email (to james at dzone dot com) and we can discuss further.
    Thanks
    James

    ReplyDelete
  2. I would guess you are running HotSpot in server mode, which it seems does optimise static initialisers (I didn't know that - very interesting finding!). However, if you have access to a 32-bit client VM, try running the same thing again. I ran in on a 3 GHz Xeon, CPU, and gave up waiting for the third loop after a few minutes.

    ReplyDelete
  3. @Vyadh, Interesting. I don't have much experience using the client JVM. I have only ever used the server JVM.

    ReplyDelete

Post a Comment

Popular posts from this blog

Java is Very Fast, If You Don’t Create Many Objects

Low Latency Microservices, A Retrospective

Unusual Java: StackTrace Extends Throwable