How can I determine why the Hotspot JVM decided to re-compile already JIT:ed code a second time?
Asked Answered
A

1

17

I'm trying to write a warm-up routine for a latency sensitive java application in order to optimize the first few transactions that would otherwise be slowed down by dynamic class loading and JIT (mainly).

The problem I'm facing is that even though my warmup code loads all classes and exercises them by calling them many times (at least 100 times -XX:CompileThreshold), later when the actual user logs on these same functions are still marked as "non entrant" and re-compiled again, which causes a latency hit.

The JVM flags are as follows (I only added -XX:+PrintCompilation -verbose:class tp troubleshoot, the others are legacy ):

-Xms5g -Xmx5g -server -XX:+AggressiveHeap -XX:+UseFastAccessorMethods -XX:+PrintGCDetails -XX:CompileThreshold=100 -XX:-CITime -XX:-PrintGC -XX:-PrintGCTimeStamps -XX:+PrintCompilation -verbose:class

#Warmup happens here
  12893 2351       my.test.application.hotSpot (355 bytes)
#Real user logs on here
 149755 2351      made not entrant  my.test.application.hotSpot (355 bytes)
 151913 2837       my.test.application.hotSpot (355 bytes)
 152079 2351      made zombie  my.test.application.hotSpot (355 bytes)

No class loading happens after the warmup (I can see the class loading before though so the flag is working).

It would appear that the function gets a new ID ( 2351 vs 2837 ) which means that somehow it is deemed as "different" by the JVM.

And how can I determine why the JVM decided to recompile this function ?

And I guess that boils down to how can I determine why the ID changed ? What are the criteria ?

I tried marking as many methods and classes as private as I could but to no avail.

This is JRE 1.6.0_45-b06.

Any tips for how to troubleshoot or get more info appreciated ! : )

Audacity answered 1/5, 2014 at 6:47 Comment(8)
possible duplicate of java PrintCompilation output: what's the meaning of "made not entrant" and "made zombie"Arris
We had a guest lecture on the Hotspot JVM recently and people from Oracle came and talked about it. They said that there are two compilation rounds in the JVM: one that compiles code that has been run 2000 times (default) and another one, that optimizes the code even more, after the code has run 10000 times... Im no expert but maybe that's why the code gets recompiled and the older compiled code marked invalid...?Global
@Global Sounds like "Tiered compilation" to me. It isn't enabled by default, AFAIK.Haldi
That’s the big strength of environments with managed code. If the use case changes, the JVM can adapt to to it. So if your warm up is calling you methods in a different way than the real user does (which is very likely), the JVM will re-optimize to the real use case.Contrary
Try running with -XX:+LogCompilation -XX:+UnlockDiagnosticVMOptions iirc that lists the deopt reason (can't check on phone atm).Additional
If you care about latency, get off Java 6 immediately. Java 8 is at least 40-50% faster.Puduns
@kittylyst We're getting off java altogether actually : ) We not only care about low latency, but about CONSISTENT low latency and java simply cannot deliver on that. The GC, threads, JIT makes for a very complicated base framework and building anything truly fast on top of that is simply impossible. For reference, we can do <10us latency so java is really really good given all the magic stuff that goes on beyond the scenes but not good enough unfortunately, which is a shame because it has many other operational benefits that are nice ... oh well ...Audacity
@Audacity Ensure that your new platform runs on bare metal. Running virtualized will cause very similar performance characteristics, in terms of scheduling uncertainty - but with the added problem that because they're happening below the (virtualized) OS most tools won't spot them.Puduns
A
14

For posterity, it was fairly simple once I read some of the source code of the hotspot JVM.

The following flags would point out the exact source code line that lead to a function being de-optimized and re-compiled:

-XX:+TraceDeoptimization -XX:+WizardMode -XX:+PrintNativeNMethods -XX:+PrintDependencies -XX:+DebugDeoptimization -XX:+LogEvents

Usually it was an if-statement like this.

void function (Object object){
    if ( object == null ){
        // do some uncommon cleanup or initialization
    }
    do_stuff();
}

Let's say my warmup code never triggered the if statement.

I had assumed that the whole function would be compiled in one go, however, when the JIT C2 compiler actually does decide to produce native code for this function, it will not generate any code for the if-statement because that code path has never been taken.

It will only generate a conditional branch that generates a trap and exception handler in the C2 compiler thread. I think this happens because the native code cache was/is fairly small and so the JVM writer did not want to fill it with potentially useless code.

Anyway, if the statement is ever true (i.e the object is ever null), then the function will immediately and unconditionally trigger this exception handling and be re-compiled ( leading to a freeze/latency hit in the order of a couple ms ).

Of course my warmup code would not call each function in the exact same way as production and I would venture to guess that in any complex product this is close to impossible and a maintenance nightmare anyway.

What this means is that for effectively warming up a java application, every single if-statement in the code needs to be called by th warmup code.

And so we are going to simply abandon the idea of "warming up" our java code because it is not as simple as some would believe.

For the following reasons, we are going to re-write parts of the application to support being ran for weeks/months at a time:

  • Easier maintenance (we don't need to simulate production during warmup and keep it updated)
  • The JIT will not be done according to our plastic simulations but instead production behaviour (i.e. use the JIT for what it was designed for instead of fighting it)

Long-term the customer will likely pay for a rewrite in C/C++ or the like to get consistently low-latency but that's for another day.

EDIT: Let me just add that updating to a newer version of the hotspot JVM or "tuning" around the hotspot JVM parameters will never resolve this issue. They are both smokes and mirrors. The fact is that the hotspot JVM was never written for predictable low-latency and this shortcoming is impossible to work around from within the java userland.

Audacity answered 9/5, 2014 at 3:9 Comment(5)
Abandoning the idea of “warming up” is probably the best idea ever. Note that if the warming up is taking every conditional branch you might give the HotSpot optimizer a wrong impression about the likeliness of the condition’s result. This can lead to poor performance as well. By the way, updating to a newer JVM than Java 6 might help as well and will be cheaper than rewriting an application in C/C++.Contrary
@Contrary Also updating to a newer JVM will not help because java is fundamentally flawed in this aspect. It was never written for predictable low-latency. No upgrades or java "tuning" will ever solve this shortcoming.Audacity
This is not correct. There is no “fundamentally flaw”. Only this particular JVM is not designed for predictable low-latency (while specialized JVMs providing such feature are usually costly). But the main problem seems that you are focusing on the de-optimization event rather than your declared goal. If you want predictable latency you can simply turn off the JIT compiler. But I would rather focus on low-latency by combining most recent JVMs with up-to-date hardware to get the worst-case below the required threshold.Contrary
@Contrary Even if you used the dev tree of HotSpot it will never make a decent environment for soft-realtime programs - it was never designed to do so. If you need soft realtime constraints you want to use something that was designed with those things in mind, particularly wrt GC. There are JVMs that do exactly that, so I'm not entirely sure why anyone would want to fight with HotSpot to achieve that.. obviously the whole thing has nothing at all to do with java the language..Additional
You must have a debug version of the JVM to use these flags! Just so you know :)Tila

© 2022 - 2024 — McMap. All rights reserved.