Java uses thread local allocation buffers such that objects allocated one after another are typically contiguous in RAM. Most modern Java gcs are also compacting, meaning that the heap ends up approximately breadth first ordered after GC.
What this means is that in practice, pointer chasing is less of an issue than you’d expect. Even a linked list will end up with decent cache locality.
Obviously this won’t always work, but it generally works a lot better than the same structure in a systems language.
https://shipilev.net/jvm/anatomy-quarks/11-moving-gc-localit...