Istnieje wiele podobieństw między obiema implementacjami (i moim zdaniem: tak, obie są „maszynami wirtualnymi”).
Po pierwsze, oba są maszynami wirtualnymi opartymi na stosie, bez pojęcia „rejestrów”, do których przyzwyczailiśmy się w nowoczesnych procesorach, takich jak x86 lub PowerPC. Obliczenie wszystkich wyrażeń ((1 + 1) / 2) jest wykonywane przez umieszczenie operandów na „stosie”, a następnie zdejmowanie tych operandów ze stosu, ilekroć instrukcja (dodawanie, dzielenie itp.) Wymaga użycia tych operandów. Każda instrukcja odkłada swoje wyniki z powrotem na stos.
Jest to wygodny sposób na zaimplementowanie maszyny wirtualnej, ponieważ prawie każdy procesor na świecie ma stos, ale liczba rejestrów jest często inna (a niektóre rejestry są specjalnego przeznaczenia, a każda instrukcja oczekuje swoich operandów w różnych rejestrach itp. ).
Tak więc, jeśli zamierzasz modelować abstrakcyjną maszynę, model oparty wyłącznie na stosie jest całkiem niezłym rozwiązaniem.
Oczywiście prawdziwe maszyny nie działają w ten sposób. Zatem kompilator JIT jest odpowiedzialny za przeprowadzanie „rejestracji” operacji na kodzie bajtowym, zasadniczo planując rzeczywiste rejestry procesora tak, aby zawierały argumenty i wyniki, gdy tylko jest to możliwe.
Myślę więc, że jest to jedna z największych cech wspólnych między CLR i JVM.
Co do różnic ...
Jedną interesującą różnicą między tymi dwiema implementacjami jest to, że środowisko CLR zawiera instrukcje dotyczące tworzenia typów ogólnych, a następnie stosowania specjalizacji parametrycznych do tych typów. Tak więc w czasie wykonywania środowisko CLR uważa List <int> za zupełnie inny typ niż List <String>.
Pod okładkami używa tego samego MSIL dla wszystkich specjalizacji typu referencyjnego (więc List <String> używa tej samej implementacji co List <Object>, z różnymi rzutowaniami typu na granicach API), ale każdy typ wartości używa swoją własną unikalną implementację (List <int> generuje zupełnie inny kod niż List <double>).
W Javie typy ogólne to czysto sztuczka kompilatora. JVM nie ma pojęcia, które klasy mają argumenty typu, i nie może wykonywać specjalizacji parametrycznych w czasie wykonywania.
Z praktycznego punktu widzenia oznacza to, że nie można przeciążać metod Java na typach ogólnych. Nie możesz mieć dwóch różnych metod o tej samej nazwie, różniących się tylko tym, czy akceptują List <String>, czy List <Date>. Oczywiście, ponieważ środowisko CLR wie o typach parametrycznych, nie ma problemu z obsługą metod przeciążonych w specjalizacjach typów ogólnych.
Na co dzień to różnica, którą najbardziej zauważam między CLR a JVM.
Inne ważne różnice obejmują:
Środowisko CLR ma zamknięcia (zaimplementowane jako delegaci C #). JVM obsługuje zamknięcia tylko od wersji Java 8.
Środowisko CLR zawiera procedury (zaimplementowane za pomocą słowa kluczowego „yield” w języku C #). JVM tego nie robi.
Środowisko CLR umożliwia kodowi użytkownika definiowanie nowych typów wartości (struktur), podczas gdy JVM zapewnia stałą kolekcję typów wartości (bajt, krótka, int, długa, zmiennoprzecinkowa, podwójna, znakowa, logiczna) i pozwala tylko użytkownikom definiować nowe odniesienia typy (klasy).
Środowisko CLR zapewnia obsługę deklarowania wskaźników i manipulowania nimi. Jest to szczególnie interesujące, ponieważ zarówno maszyna JVM, jak i środowisko CLR wykorzystują implementacje modułu wyrzucania elementów bezużytecznych w postaci ścisłego generowania kompaktowania jako strategii zarządzania pamięcią. W zwykłych okolicznościach ścisłe kompaktowanie GC ma naprawdę trudne chwile ze wskaźnikami, ponieważ gdy przenosisz wartość z jednej lokalizacji pamięci do drugiej, wszystkie wskaźniki (i wskaźniki do wskaźników) stają się nieważne. Jednak środowisko CLR zapewnia mechanizm „przypinania”, dzięki czemu programiści mogą zadeklarować blok kodu, w ramach którego CLR nie może przenosić pewnych wskaźników. To bardzo wygodne.
Największą jednostką kodu w JVM jest `` pakiet '', o czym świadczy słowo kluczowe `` chronione '' lub prawdopodobnie JAR (tj. Java ARchive), o czym świadczy możliwość określenia jar w ścieżce klas i traktowania go jak folderu kodu. W środowisku CLR klasy są agregowane w „zestawy”, a środowisko CLR zapewnia logikę rozumowania i manipulowania zestawami (które są ładowane do „AppDomains”, udostępniając obszary izolowane na poziomie aplikacji do przydzielania pamięci i wykonywania kodu).
Format kodu bajtowego CLR (złożony z instrukcji MSIL i metadanych) ma mniej typów instrukcji niż JVM. W JVM każda unikalna operacja (dodanie dwóch wartości int, dodanie dwóch wartości zmiennoprzecinkowych itp.) Ma swoją własną unikalną instrukcję. W środowisku CLR wszystkie instrukcje MSIL są polimorficzne (dodaj dwie wartości), a kompilator JIT jest odpowiedzialny za określenie typów operandów i utworzenie odpowiedniego kodu maszynowego. Nie wiem jednak, która strategia jest preferowana. Oba mają kompromisy. Kompilator HotSpot JIT dla JVM może używać prostszego mechanizmu generowania kodu (nie musi określać typów operandów, ponieważ są one już zakodowane w instrukcji), ale oznacza to, że potrzebuje bardziej złożonego formatu kodu bajtowego, z większą liczbą typów instrukcji.
Używam Javy (i podziwiam JVM) od około dziesięciu lat.
Ale moim zdaniem CLR jest teraz lepszą implementacją, prawie pod każdym względem.