ThreadLocalHandshakes introduce a diagnostic flag to abort the VM if a handshake takes more than some set time:
java -XX:+UnlockDiagnosticVMOptions -XX:HandshakeTimeout=20 -version
Setting this to a low value, like 5 or 10 (ms) makes the VM likely to abort on startup on my machine:
$ java -XX:+UnlockDiagnosticVMOptions -XX:HandshakeTimeout=10 -version
#
# A fatal error has been detected by the Java Runtime Environment:
#
This does not happen with -Xint, -XX:-TieredCompilation or even -XX:TieredStopAtLevel=1, all these get through startup just fine even with -XX:HandshakeTimeout=1.
This indicates there's a handshake blocked by a compiler thread for an excessive amount of time, which consistently happen during startup. This appears to be a major contributor to increased work during startup since the handshake is causing excessive spin.
This turns out to be due to interaction between the NMethod sweeper thread - which is started very early and attempts early handshakes in NMethodSweeper::do_stack_scanning - and the stub code generation in OptoRuntime::generate(ciEnv* env) which compiles code in VM mode for ~10ms. Moving this to native mode resolves the issue, but a simpler and safer fix is to instruct the NMethod Sweeper thread not to do the scan on first run.