hashCode
is defined in java.lang.Object
, so defining it in your own class doesn’t do much at all. (still it’s a defined method but it makes no difference)
JIT has several ways to optimize call sites (in this case hashCode()
):
- no overrides – static call (no virtual at all) – best case scenario with full optimizations
- 2 sites – ByteBuffer for instance: exact type check and then static dispatch. The type check is very simple but depending on the usage it may or may not be predicted by the hardware.
- inline caches – when few different class instances have been used in the caller body, it’s possible to keep them inlined too – that’s it some methods might be inlined, some may be called via virtual tables. Inline budget is not very high. This is exactly the case in the question – a different method not named hashCode() would feature the inline caches as there are only four implementations, instead of the v-table
- Adding more classes going through that caller body results in real virtual call as the compiler gives up.
The virtual calls are not inlined and require an indirection through the table of virtual methods and virtually ensured cache miss. The lack of inlining actually requires full function stubs with parameters passed through the stack. Overall when the real performance killer is the inability to inline and apply optimizations.
Please note: calling hashCode()
of any class extending Base is the same as calling Object.hashCode()
and this is how it compiles in the bytecode, if you add an explicit hashCode in Base that would limit the potential call targets invoking Base.hashCode()
.
Way too many classes (in JDK itself) have hashCode()
overridden so in cases on not inlined HashMap alike structures the invocation is performed via vtable – i.e. slow.
As extra bonus: While loading new classes the JIT has to deoptimize existing call sites.
I may try to look up some sources, if anyone is interested in further reading