> even final methods are just an agreement with the compiler, the VM doesn't care, it will dynamically look up the method
You’re right about the “final” keyword being a placebo, but you got the rest exactly backwards.
The JVM is ridiculously aggressive in optimizing for throughout over latency: It assumes that everything is final and compiles the code with that assumption until proven otherwise. If it sees a method getting overridden, it will go back and recompile all the callers and everything that was incorrectly inlined.
A lot of Java code depends on this. For example if you only load one of several plugins at runtime, there’s no overhead vs implementing that plugin’s feature in the main code base.
A single plugin case is a kinda optimistic wishful thinking. Sure, this case happens. Sometimes.
But in real code you often have plenty of things like iterators or lambdas, and you'll have many of types of those. So the calls to them will be megamorphic and no JVM magic can do anything about it.
While in C++ world you'd use templates or in Rust you'd use traits, which are essentially zero cost, guaranteed.
Somehow I never noticed it happening in practice. In all the cases where cache was the problem, it was caused by data, not code. CPUs prefetch the code into cache quite well.
They are kinda related in a way that Java implementation of generics does not help with devirtualization, while C++ templates / Rust traits do help by not needing virtual calls from the start.
If you load more than one Comparator type, then the calls to comparator are megamorphic and devirtualization won't happen unless the whole sort is inlined.
In languages like C++, you'd make it a template and the compiler would always know the target type, so no need for virtual.
You’re right about the “final” keyword being a placebo, but you got the rest exactly backwards.
The JVM is ridiculously aggressive in optimizing for throughout over latency: It assumes that everything is final and compiles the code with that assumption until proven otherwise. If it sees a method getting overridden, it will go back and recompile all the callers and everything that was incorrectly inlined.
A lot of Java code depends on this. For example if you only load one of several plugins at runtime, there’s no overhead vs implementing that plugin’s feature in the main code base.