Maszyna wirtualna Java i środowisko CLR

140

Jako rodzaj kontynuacji pytania o nazwie Różnice między kodem bajtowym MSIL i kodem bajtowym Javy? , jakie są (główne) różnice lub podobieństwa między sposobem działania wirtualnej maszyny języka Java a sposobem działania.NET Framework Common Language Runtime (CLR) działa?

Również jest .NET Framework CLR „maszyna wirtualna” czy nie ma atrybutów maszyny wirtualnej?

Frank V
źródło
Cóż, jeśli porównujesz coś podobnego i podobnego, powinieneś przeformułować pytanie jako różnicę między maszyną wirtualną a CLR (Common Language Runtime), który jest bezpośrednim analogiem do maszyny wirtualnej.
cletus

Odpowiedzi:

278

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.

benjismith
źródło
73
Zamknięcia i generatory są implementowane na poziomie języka i są po prostu reprezentowane jako klasy na poziomie CLR.
Curt Hagenlocher
2
A co z różnicami w sposobie radzenia sobie ze stertą? Środowisko CLR jest bardziej zależne od procedury systemu operacyjnego / hosta, podczas gdy JVM zarządza mniej lub bardziej pamięcią sterty.
Kelly S. French
6
Ważną różnicą jest kontrast między kompilacją just-in-time (CLR) a optymalizacją adaptacyjną w (Oracle / Sun) JVM.
Edwin Dalorzo
1
Gniazda zmiennych lokalnych Java działają podobnie jak rejestry. Ale to wszystko i tak dyskusyjne, ponieważ JIT zamienia lokalne sloty i stos w prawdziwe rejestry.
Antymon
1
@kuhajeyan to dlatego, że kiedy wprowadzono CLR, JVM miała 10 lat. to dużo czasu w IT. Kiedy pojawił się JVM w 1993 r., Nie było poważnego kandydata, w przypadku CLR (2003) istniał dojrzały i solidny JVM z silnym przyczółkiem w przemyśle.
Simple Fellow
25

Twoje pierwsze pytanie dotyczy porównania JVM z .NET Framework - zakładam, że zamiast tego miałeś zamiar porównać z CLR. Jeśli tak, myślę, że mógłbyś napisać małą książkę na ten temat ( EDYCJA: wygląda na to, że Benji już ma :-)

Jedną ważną różnicą jest to, że środowisko CLR zostało zaprojektowane jako architektura neutralna językowo, w przeciwieństwie do JVM.

Inną ważną różnicą jest to, że środowisko CLR zostało specjalnie zaprojektowane, aby umożliwić wysoki poziom współdziałania z kodem natywnym. Oznacza to, że środowisko CLR musi zarządzać niezawodnością i bezpieczeństwem, gdy uzyskuje się dostęp do pamięci natywnej i modyfikuje ją, a także zarządza organizowaniem między strukturami danych opartymi na CLR a natywnymi strukturami danych.

Odpowiadając na drugie pytanie, termin „maszyna wirtualna” jest starszym terminem ze świata sprzętu (np. Wirtualizacja modelu 360 przez IBM w latach 60.), który kiedyś oznaczał programową / sprzętową emulację maszyny bazowej w celu osiągnięcia tego samego rodzaju. rzeczy, które robi VMWare.

CLR jest często określany jako „silnik wykonawczy”. W tym kontekście jest to implementacja maszyny IL na szczycie platformy x86. To właśnie robi JVM, chociaż można argumentować, że istnieje ważna różnica między polimorficznymi kodami bajtowymi CLR a typowanymi kodami bajtowymi JVM.

Więc pedantyczna odpowiedź na twoje drugie pytanie brzmi „nie”. Ale tak naprawdę sprowadza się do tego, jak zdefiniujesz te dwa terminy.

EDYCJA: Jeszcze jedna różnica między JVM a CLR polega na tym, że JVM (wersja 6) bardzo niechętnie zwalnia przydzieloną pamięć z powrotem do systemu operacyjnego, nawet jeśli jest to możliwe.

Na przykład, powiedzmy, że proces JVM uruchamia się i początkowo przydziela 25 MB pamięci z systemu operacyjnego. Następnie kod aplikacji podejmuje próbę alokacji, które wymagają dodatkowych 50 MB. Maszyna JVM przydzieli dodatkowe 50 MB z systemu operacyjnego. Gdy kod aplikacji przestanie korzystać z tej pamięci, jest ona usuwana z pamięci, a rozmiar sterty maszyny JVM zmniejszy się. Jednak maszyna JVM zwalnia przydzieloną pamięć systemu operacyjnego tylko w określonych, bardzo szczególnych okolicznościach . W przeciwnym razie przez pozostałą część okresu istnienia procesu ta pamięć pozostanie przydzielona.

Z drugiej strony CLR zwalnia przydzieloną pamięć z powrotem do systemu operacyjnego, jeśli nie jest już potrzebna. W powyższym przykładzie CLR zwolniłoby pamięć po zmniejszeniu sterty.

HTTP 410
źródło
2
To absolutnie nieprawda, że ​​JVM nie zwolni przydzielonej pamięci. Zobacz moją odpowiedź na to pytanie na dowód: stackoverflow.com/questions/366658/…
Michael Borgwardt
Widziałem, jak maszyna JVM zwracała pamięć z powrotem do systemu Windows.
Steve Kuo
Zmieniłem swoją odpowiedź, mówiąc, że JVM 6 bardzo niechętnie zwalnia pamięć, z linkami do odpowiedzi Ran i Michaela. Nigdy nie widziałem tego zachowania w JVM 5, więc może ta wersja była jeszcze bardziej niechętna.
HTTP 410
Czy mógłbyś opisać, w jaki sposób maszyna JVM aktywnie zarządza stertą, podczas gdy środowisko CLR opiera się na procesie nadrzędnym? Konkretnym przykładem, którego używam, jest JVM ma argumenty wykonawcze dla maksymalnego rozmiaru sterty, podczas gdy domyślne środowisko CLR nie. Chociaż prawdą jest, że aplikacja CLR hostowana w ramach usług IIS może skonfigurować usługi IIS w celu ograniczenia pamięci, oznaczałoby to włączenie usług IIS do definicji maszyny wirtualnej.
Kelly S. Francuski
@Steve Kuo, tak, też to widziałem. zwykle między 17:00 a 18:00.
Simple Fellow
11

CLR i JVM są maszynami wirtualnymi.

.NET Framework i Java Runtime Environment są pakietami odpowiednich maszyn wirtualnych i ich bibliotek. Bez bibliotek maszyny wirtualne są całkiem bezużyteczne.

Allain Lalonde
źródło
11

Więcej informacji na temat różnic można znaleźć w różnych źródłach akademickich i prywatnych. Kiedyś dobrym przykładem są CLR Design Choices .

Niektóre konkretne przykłady obejmują:

  • Niektóre opcje niskopoziomowe są wpisywane, na przykład „dodaj dwie liczby int”, gdzie CLR używa operandu polimorficznego. (tj. fadd / iadd / ladd vs po prostu dodaj)
  • Obecnie JVM wykonuje bardziej agresywne profilowanie i optymalizację środowiska wykonawczego (np. Hotspot). CLR obecnie przeprowadza optymalizacje JIT, ale nie optymalizuje czasu wykonywania (tj. Zastępuje kod podczas działania).
  • CLR nie wbudowuje metod wirtualnych, JVM robi ...
  • Obsługa typów wartości w środowisku CLR poza tylko „elementami podstawowymi”.
James Schek
źródło
-11

Nie jest to maszyna wirtualna, platforma .net kompiluje zestawy do natywnych plików binarnych w czasie pierwszego uruchomienia:

W komputerach kompilacja just-in-time (JIT), znana również jako tłumaczenie dynamiczne, jest techniką poprawy wydajności programu komputerowego w czasie wykonywania. JIT opiera się na dwóch wcześniejszych pomysłach w środowiskach wykonawczych: kompilacji kodu bajtowego i kompilacji dynamicznej. Konwertuje kod w czasie wykonywania, przed wykonaniem go natywnie, na przykład kod bajtowy, na natywny kod maszynowy. Poprawa wydajności w porównaniu z interpreterami wynika z buforowania wyników tłumaczenia bloków kodu, a nie tylko z ponownej oceny każdego wiersza lub operandu za każdym razem, gdy jest on spełniony (patrz Język interpretowany). Ma również zalety w stosunku do statycznej kompilacji kodu w czasie programowania, ponieważ może przekompilować kod, jeśli okaże się to korzystne, i może być w stanie wymusić gwarancje bezpieczeństwa.

Kilka nowoczesnych środowisk wykonawczych, takich jak Microsoft .NET Framework, większość implementacji języka Java, a ostatnio Actionscript 3, polegają na kompilacji JIT w celu szybkiego wykonywania kodu.

Źródło: http://en.wikipedia.org/wiki/Just-in-time_compilation

Dodanie .NET Framework zawiera maszynę wirtualną, podobnie jak Java.

Diones
źródło
10
Tylko dlatego, że maszyna wirtualna wykorzystuje JIT do optymalizacji wydajności, nie oznacza, że ​​nie jest już maszyną wirtualną. Kiedy programista kompiluje, kompiluje do maszyny wirtualnej, pozostawiając implementacji wykonanie wykonania w dowolny sposób
Allain Lalonde