У CLR и JVM есть цели и принципы, которые отличаются больше, чем вы думаете. В целом, JVM направлена на оптимизацию более динамического высокоуровневого кода, в то время как CLR предоставляет вам более низкоуровневые инструменты для выполнения этих видов оптимизации самостоятельно.
Хорошим примером является распределение стека. В CLR у вас есть явное распределение в стеке пользовательских типов значений. В JVM единственные пользовательские типы являются ссылочными типами, но JVM может преобразовывать выделения кучи в распределения стека при определенных обстоятельствах с помощью Escape-анализа.
Еще один пример. В Java методы являются виртуальными по умолчанию. На C # по крайней мере их нет. Гораздо сложнее оптимизировать вызовы виртуальных методов, поскольку код, который выполняется на данном сайте вызовов, не может быть определен статически.
Под капотом их исполнительные системы совершенно разные. Большинство JVM (в частности, Hotspot) начинаются с интерпретатора байт-кода и только JIT-компилируемых частей кода, которые выполняются интенсивно, например. плотные петли. Они также могут перекомпилировать их снова и снова, используя статистику выполнения, собранную из предыдущих запусков, для обеспечения оптимизации. Это позволяет приложить больше усилий по оптимизации к тем частям программы, которые в этом больше всего нуждаются. Это называется адаптивной оптимизацией.
CLR компилирует все заранее только один раз. Он выполняет меньше оптимизаций, потому что у него больше кода для компиляции и, следовательно, он должен быть быстрым, а также потому, что у него нет статистики по фактическим путям выполнения, взятым для его оптимизации. Этот подход имеет очень существенное преимущество, заключающееся в том, что вы можете кэшировать результаты компиляции между процессами, что делает CLR, а JVM нет.
Большой процент кода Jotsp Hotspot посвящен этим адаптивным оптимизациям, и именно они ставят Java в тот же уровень производительности, что и собственный код, для большинства вычислений общего назначения в начале 2000-х годов. Они также делают JVM достойной целью для динамических языков. Здесь я исключаю более недавние разработки динамических языков исполнения и invokedynamic, поскольку я недостаточно знаю о DLR.