It is written in Java because the original devs probably did not know anything better.
Java can be ridiculously fast when only primitives are used so I wouldn’t put a quite performant game engine past it, but yeah, it would perhaps not be my first choice for a new AAA game engine.
That said, C# has value types and has for years now. Which is hugely important for arrays, and a major, major missing piece to the Java performance puzzle. C#'s FFI is also way better than the disaster that is JNI, which also plays a role here.
While Valhalla is in the works, but it is, I quote, at least 3 PhD’s worth of knowledge combined to figure it out in a backwards compatible way with all the interactions of generics, etc.
But most games don't actually need this. I'd be more concerned about whether there's a JVM language you like over other non JVM options for a game.
I develop games as a hobby, and my understanding is that this is literally true, but a little misleading. Games are as diverse as other areas of software, but they're skewed toward the more complex and demanding end. I don't think it's that atypical for a moderately complex game by an indie studio to get to the point where memory layout is a concern, which I don't think is as true for web apps, desktop apps, or other areas of development. That being said, I think worrying about memory layout is more than a lot of games need.
If you work on Android games for example would you get any kind of control over mem layout, irrespective of the stack you will be using?
class RGB { float r float g float b ... }
and then make an `ArrayList<RGB>` the list will end up using over 2x the memory (128 bytes for object headers, 64 bytes for pointers in the list) compared to a language with value types. What makes this even worse is that since you are using a list of pointers, you can't use SIMD for any of your computations, and accessing elements will be slow since the values won't be in cache.
Also, there is now a Vector API that let’s you use SIMD operations with configurable lane width (and a safe fallback to for loops for processors without the necessary instructions)
In particular for Java there's regularly dependent pointer loads, which are dreadfully slow on modern CPUs and also waste a significant amount of L1/L2 cache.