Startup cost
Native protection adds a one-time loader cost, not a per-call tax.
Startup stays flat across protection levels: turning on the VM or the obfuscator does not
slow the jar down. Only aggressive is measurably heavier.
Faster invokedynamic, natively
Chain jvmtp behind a bytecode obfuscator and it runs the obfuscator's own invokedynamic dispatchers as native code. Two views below: end-to-end on a real jar, and the per-call dispatch cost underneath it.
On a jar run through ZKM with reference obfuscation on, the input carries 749 invokedynamic dispatchers. All four VM backends decrypt and execute them; the stack backend is the shipped default, fastest and smallest.
All four backends run the ZKM jar correctly. Stack is fastest and smallest; register and dual cost little runtime but grow the jar, so they are a deliberate VM-diversity upgrade, not a speed choice.
Underneath every dispatch is a MethodHandle chain the JVM re-links and calls through generic, fully-boxed machinery. jvmtp resolves that chain once and replaces the per-call boxing with a direct native call: 5x to 9x faster per invocation on the shapes obfuscators install, and never slower on one it cannot crack.
invokeWithArguments. Cracked is a direct native call to the resolved
target. Steady-state per-call cost only; the one-time crack resolution is not counted.
4,000,000 iterations after 600,000 warmup, monotonic clock. The heaviest obfuscation
hides in the relinking call site, which cracks ~6.5x; a shape that cannot be cracked
stays on the boxed path at no penalty.
End-to-end runtime and jar size come from a VM-backend sweep on the ZKM jar. Per-call figures come from a standalone JNI microbenchmark linked against the JDK, timing the boxed path against the crack primitive per shape. Startup timing is a serial min-of-7 over the corpus jars.