Между обеими реализациями много общего (и, на мой взгляд: да, они обе «виртуальные машины»).
Во-первых, это виртуальные машины на основе стека, без понятия «регистры», как мы привыкли видеть в современных процессорах, таких как x86 или PowerPC. Оценка всех выражений ((1 + 1) / 2) выполняется путем помещения операндов в «стек» и последующего извлечения этих операндов из стека всякий раз, когда инструкция (сложение, деление и т. Д.) Требует использования этих операндов. Каждая инструкция помещает свои результаты обратно в стек.
Это удобный способ реализовать виртуальную машину, потому что почти каждый процессор в мире имеет стек, но количество регистров часто отличается (а некоторые регистры являются специальными, и каждая инструкция ожидает своих операндов в разных регистрах и т. ).
Итак, если вы собираетесь моделировать абстрактную машину, модель, основанная исключительно на стеке, - довольно хороший способ.
Конечно, настоящие машины так не работают. Таким образом, JIT-компилятор отвечает за выполнение «регистрации» операций с байт-кодом, по сути планируя фактические регистры ЦП для хранения операндов и результатов, когда это возможно.
Итак, я думаю, что это одна из самых общих черт между CLR и JVM.
Что до отличий ...
Одно интересное различие между двумя реализациями заключается в том, что среда CLR включает инструкции для создания универсальных типов, а затем для применения параметрических специализаций к этим типам. Итак, во время выполнения CLR считает List <int> совершенно другим типом, нежели List <String>.
Внутри него используется один и тот же MSIL для всех специализаций ссылочного типа (так что List <String> использует ту же реализацию, что и List <Object>, с разными типами на границах API), но каждый тип значения использует собственная уникальная реализация (List <int> генерирует совершенно отличный от List <double> код).
В Java универсальные типы - это просто уловка компилятора. JVM не знает, какие классы имеют аргументы типа, и не может выполнять параметрическую специализацию во время выполнения.
С практической точки зрения это означает, что вы не можете перегружать методы Java универсальными типами. У вас не может быть двух разных методов с одним и тем же именем, различающихся только тем, принимают ли они List <String> или List <Date>. Конечно, поскольку CLR знает о параметрических типах, у нее нет проблем с обработкой методов, перегруженных специализациями универсальных типов.
В повседневной жизни я больше всего замечаю разницу между CLR и JVM.
Другие важные отличия включают:
В CLR есть замыкания (реализованные как делегаты C #). JVM поддерживает закрытие только с Java 8.
В CLR есть сопрограммы (реализованные с помощью ключевого слова yield в C #). JVM этого не делает.
CLR позволяет пользовательскому коду определять новые типы значений (структуры), тогда как JVM предоставляет фиксированный набор типов значений (byte, short, int, long, float, double, char, boolean) и позволяет пользователям определять только новые ссылки - виды (классы).
CLR обеспечивает поддержку объявления указателей и управления ими. Это особенно интересно, потому что как JVM, так и CLR используют реализации сборщика мусора со строгим сжатием поколений в качестве стратегии управления памятью. В обычных обстоятельствах сборщик мусора со строгим уплотнением действительно испытывает трудности с указателями, потому что, когда вы перемещаете значение из одного места в памяти в другое, все указатели (и указатели на указатели) становятся недействительными. Но CLR предоставляет механизм «закрепления», чтобы разработчики могли объявить блок кода, внутри которого CLR не разрешено перемещать определенные указатели. Это очень удобно.
Самая большая единица кода в JVM - это либо «пакет», о чем свидетельствует ключевое слово «protected», либо, возможно, JAR (т.е. Java ARchive), о чем свидетельствует возможность указать jar-файл в пути к классам и обработать его как папку кода. В среде CLR классы объединены в «сборки», а среда CLR предоставляет логику для анализа сборок и управления ими (которые загружаются в «домены приложений», предоставляя «песочницы» уровня приложения для выделения памяти и выполнения кода).
Формат байт-кода CLR (состоящий из инструкций и метаданных MSIL) имеет меньше типов инструкций, чем JVM. В JVM каждая уникальная операция (добавление двух значений int, добавление двух значений с плавающей запятой и т. Д.) Имеет свою собственную уникальную инструкцию. В среде CLR все инструкции MSIL полиморфны (складываются два значения), а JIT-компилятор отвечает за определение типов операндов и создание соответствующего машинного кода. Однако я не знаю, какая стратегия предпочтительнее. У обоих есть компромиссы. JIT-компилятор HotSpot для JVM может использовать более простой механизм генерации кода (ему не нужно определять типы операндов, потому что они уже закодированы в инструкции), но это означает, что ему нужен более сложный формат байт-кода, с большим количеством типов инструкций.
Я использую Java (и восхищаюсь JVM) уже около десяти лет.
Но, на мой взгляд, CLR теперь является лучшей реализацией почти во всех отношениях.