There is nothing magical about the JIT, you can let it warm up, suspend the process and look at the compiled code with a debugger (prepare to be disappointed though). When working with primitive arrays, its not very different from C. Lack of value types / reinterpreting memory is a big problem for Java, but not so much for C# IIRC. Fingers crossed for project Valhalla :)
The only thing that is kind of opaque is the GC, you need to learn the basics of how the algorithm works. There are some surprising relevations, like time of GC being proportional to surviving objects, not dead ones. Not to mention the new generation of GCs which use all kinds of tricks inside their read/write barriers to trade a little bit of throughput for low latency.