Es gibt viele Ähnlichkeiten zwischen beiden Implementierungen (und meiner Meinung nach: Ja, beide sind "virtuelle Maschinen").
Zum einen sind sie beide stapelbasierte VMs, ohne die Vorstellung von "Registern", wie wir es von einer modernen CPU wie x86 oder PowerPC gewohnt sind. Die Auswertung aller Ausdrücke ((1 + 1) / 2) erfolgt durch Verschieben von Operanden auf den "Stapel" und anschließendes Entfernen dieser Operanden vom Stapel, wenn ein Befehl (Hinzufügen, Teilen usw.) diese Operanden verbrauchen muss. Jeder Befehl schiebt seine Ergebnisse zurück auf den Stapel.
Dies ist eine bequeme Möglichkeit, eine virtuelle Maschine zu implementieren, da so gut wie jede CPU auf der Welt über einen Stapel verfügt, die Anzahl der Register jedoch häufig unterschiedlich ist (und einige Register für spezielle Zwecke bestimmt sind und jeder Befehl seine Operanden in unterschiedlichen Registern usw. Erwartet ).
Wenn Sie also eine abstrakte Maschine modellieren möchten, ist ein rein stapelbasiertes Modell ein guter Weg.
Natürlich funktionieren echte Maschinen nicht so. Der JIT-Compiler ist also dafür verantwortlich, die "Registrierung" von Bytecode-Operationen durchzuführen und die tatsächlichen CPU-Register so zu planen, dass sie nach Möglichkeit Operanden und Ergebnisse enthalten.
Ich denke, das ist eine der größten Gemeinsamkeiten zwischen der CLR und der JVM.
Was Unterschiede betrifft ...
Ein interessanter Unterschied zwischen den beiden Implementierungen besteht darin, dass die CLR Anweisungen zum Erstellen generischer Typen und zum Anwenden parametrischer Spezialisierungen auf diese Typen enthält. Zur Laufzeit betrachtet die CLR eine Liste <int> als einen völlig anderen Typ als eine Liste <String>.
Unter dem Deckmantel wird für alle Spezialisierungen von Referenztypen dieselbe MSIL verwendet (daher verwendet eine Liste <String> dieselbe Implementierung wie eine Liste <Objekt> mit unterschiedlichen Typumwandlungen an den API-Grenzen), aber jeder Werttyp verwendet eine eigene eindeutige Implementierung (List <int> generiert völlig anderen Code als List <double>).
In Java sind generische Typen ein reiner Compilertrick. Die JVM hat keine Ahnung, welche Klassen Typargumente haben, und kann zur Laufzeit keine parametrischen Spezialisierungen durchführen.
Aus praktischer Sicht bedeutet dies, dass Sie Java-Methoden für generische Typen nicht überladen können. Sie können nicht zwei verschiedene Methoden mit demselben Namen verwenden, die sich nur darin unterscheiden, ob sie eine Liste <String> oder eine Liste <Datum> akzeptieren. Da die CLR über parametrische Typen Bescheid weiß, gibt es natürlich keine Probleme bei der Behandlung von Methoden, die bei generischen Typenspezialisierungen überladen sind.
Das ist der Unterschied, den ich täglich am meisten zwischen der CLR und der JVM bemerke.
Weitere wichtige Unterschiede sind:
Die CLR verfügt über Schließungen (implementiert als C # -Delegierte). Die JVM unterstützt Schließungen erst seit Java 8.
Die CLR verfügt über Coroutinen (implementiert mit dem Schlüsselwort C # 'yield'). Die JVM nicht.
Mit der CLR kann der Benutzercode neue Werttypen (Strukturen) definieren, während die JVM eine feste Sammlung von Werttypen (Byte, Short, Int, Long, Float, Double, Char, Boolean) bereitstellt und nur neue Referenztypen definieren kann. Typen (Klassen).
Die CLR bietet Unterstützung für das Deklarieren und Bearbeiten von Zeigern. Dies ist besonders interessant, da sowohl die JVM als auch die CLR strenge Speicher-Collector-Implementierungen zur Komprimierung von Generationen als Speicherverwaltungsstrategie verwenden. Unter normalen Umständen hat ein strikter Komprimierungs-GC Schwierigkeiten mit Zeigern, da beim Verschieben eines Werts von einem Speicherort zu einem anderen alle Zeiger (und Zeiger auf Zeiger) ungültig werden. Die CLR bietet jedoch einen "Pinning" -Mechanismus, mit dem Entwickler einen Codeblock deklarieren können, in dem die CLR bestimmte Zeiger nicht verschieben darf. Es ist sehr praktisch.
Die größte Codeeinheit in der JVM ist entweder ein 'Paket', wie durch das Schlüsselwort 'protected' belegt, oder eine JAR (dh Java ARchive), wie durch die Möglichkeit angegeben, ein JAR im Klassenpfad anzugeben und es wie einen Ordner behandeln zu lassen von Code. In der CLR werden Klassen zu 'Assemblys' zusammengefasst, und die CLR bietet Logik zum Überlegen und Bearbeiten von Assemblys (die in "AppDomains" geladen werden und Sandboxen auf Subanwendungsebene für die Speicherzuweisung und Codeausführung bereitstellen).
Das CLR-Bytecode-Format (bestehend aus MSIL-Anweisungen und Metadaten) hat weniger Befehlstypen als die JVM. In der JVM hat jede eindeutige Operation (zwei int-Werte hinzufügen, zwei float-Werte hinzufügen usw.) eine eigene eindeutige Anweisung. In der CLR sind alle MSIL-Anweisungen polymorph (fügen Sie zwei Werte hinzu), und der JIT-Compiler ist dafür verantwortlich, die Typen der Operanden zu bestimmen und den entsprechenden Maschinencode zu erstellen. Ich weiß jedoch nicht, welche Strategie am besten geeignet ist. Beide haben Kompromisse. Der HotSpot JIT-Compiler für die JVM kann einen einfacheren Mechanismus zur Codegenerierung verwenden (er muss keine Operandentypen bestimmen, da diese bereits in der Anweisung codiert sind). Dies bedeutet jedoch, dass ein komplexeres Bytecode-Format erforderlich ist. mit mehr Anweisungstypen.
Ich benutze Java (und bewundere die JVM) seit ungefähr zehn Jahren.
Aber meiner Meinung nach ist die CLR jetzt in fast jeder Hinsicht die überlegene Implementierung.