My understanding, which may be wrong as I know a lot more about the HotSpot and ART JVMs than CoreCLR, is that the differences boil down to:

• CoreCLR has support for some static language features that C# has but Java lacks, like ability to embed unsafe code easily. In theory you can do that in Java too using sun.misc.Unsafe but nobody actually does.

• HotSpot has historically had a tighter and more focused approach to compilation, with profile guided optimisation being relatively more important. HotSpot always starts executing code with a (highly optimised) interpreter, to gather profile data. The .NET runtime in contrast, compiles all code immediately on first run, and relies on more traditional compiler techniques with less profile guided and speculative optimisation. They seem to have gone back and forth on different compilation strategies and haven't been able to really pick one: e.g. they started out with a simple straight-line JITC, then they rewrote it a couple of times (?), then they did NGen for ahead of time compilation based on the MSVC++ backend, and also something called RyuJIT that is (again) an immediate mode compiler without any kind of interpreted guidance. HotSpot also has multiple compilers and always has, but the general design seems more stable - interpreter for gathering data, then a quick compile by C1, and then another slower compile by C2 for really hot methods.

• As just mentioned, .NET can do ahead of time compilation when an app is installed, whereas HotSpot cannot. The ART runtime used on Android nowadays also does AOT compilation (actually a mix of AOT and JIT). It seems like AOT works better for desktop style apps where the overhead of a JIT compiler is harder to tolerate and the benefits are smaller. As .NET has had a stronger desktop focus for longer it's not really a big surprise that .NET has experimented with this more, whereas the HotSpot guys and many of the competing JVM vendors focused exclusively on the server for a long time.

• HotSpot has focused in recent years a lot more on dynamic language support, as far as I can tell .NET's support for this isn't as good. HotSpot supports "invokedynamic" which is basically a very advanced programmable dynamic linker, and gives a major performance boost to implementations of dynamic scripting languages like Javascript, Ruby, Python, etc. The post by pron below goes into more detail on Graal/Truffle which I agree is very interesting work, where they're pushing profile guided JITC technology dramatically further and represents a major change to the JVM's architecture (no more bytecode, for dynamic languages).

• HotSpot has several garbage collectors where as CoreCLR only seems to have one. Each HotSpot GC represents a different set of tradeoffs. The most advanced is called G1 but it's intended for servers that have lots of memory as the additional RAM overhead it imposes is somewhat high, which is why HotSpot still supports ParCMS (parallel concurrent mark sweep). For more desktopy workloads ParCMS is lower overhead and pause times are still OK. G1 has a lot of nifty features, for instance it can deduplicate strings that are lying around in RAM. I think the .NET GC is simply not as good as G1 and this tends to show up in benchmarks.

As an aside, the HotSpot/OpenJDK source code is dramatically easier to read and less messy than the .NET source code. The G1 collector code for instance is neatly modularised into different files, the classes are straightforward, etc. The .NET GC is a giant single C++ file so large that github refuses to render it. It's also riddled with #ifdefs and utility functions scattered around without any apparent rhyme or reason.