Ci sono molte somiglianze tra entrambe le implementazioni (e secondo me: sì, sono entrambe "macchine virtuali").
Per prima cosa, sono entrambe macchine virtuali basate su stack, senza la nozione di "registri" come siamo abituati a vedere in una CPU moderna come x86 o PowerPC. La valutazione di tutte le espressioni ((1 + 1) / 2) viene eseguita spingendo gli operandi sullo "stack" e quindi estraendo quegli operandi dallo stack ogni volta che un'istruzione (add, divide, ecc.) Deve consumare quegli operandi. Ogni istruzione rimanda i suoi risultati allo stack.
È un modo conveniente per implementare una macchina virtuale, perché praticamente tutte le CPU del mondo hanno uno stack, ma il numero di registri è spesso diverso (e alcuni registri sono specifici, e ogni istruzione prevede i suoi operandi in registri diversi, ecc. ).
Quindi, se hai intenzione di modellare una macchina astratta, un modello puramente basato su stack è un buon modo per andare.
Naturalmente, le macchine reali non funzionano in questo modo. Quindi il compilatore JIT è responsabile dell'esecuzione della "registrazione" delle operazioni bytecode, essenzialmente programmando i registri effettivi della CPU per contenere operandi e risultati ogni volta che è possibile.
Quindi, penso che sia uno dei maggiori punti in comune tra CLR e JVM.
Per quanto riguarda le differenze ...
Una differenza interessante tra le due implementazioni è che il CLR include istruzioni per la creazione di tipi generici e quindi per l'applicazione di specializzazioni parametriche a tali tipi. Quindi, in fase di esecuzione, il CLR considera un Elenco <int> come un tipo completamente diverso da un Elenco <String>.
Sotto le copertine, utilizza lo stesso MSIL per tutte le specializzazioni del tipo di riferimento (quindi un Elenco <String> utilizza la stessa implementazione di un Elenco <Oggetto>, con cast di tipi diversi ai limiti dell'API), ma ogni tipo di valore utilizza la sua implementazione unica (Elenco <int> genera codice completamente diverso dall'elenco <doppio>).
In Java, i tipi generici sono puramente un trucco da compilatore. JVM non ha idea di quali classi abbiano argomenti di tipo e non è in grado di eseguire specializzazioni parametriche in fase di esecuzione.
Da un punto di vista pratico, ciò significa che non è possibile sovraccaricare i metodi Java su tipi generici. Non puoi avere due metodi diversi, con lo stesso nome, differenti solo se accettano un Elenco <String> o un Elenco <Data>. Naturalmente, poiché il CLR conosce i tipi parametrici, non ha problemi a gestire i metodi sovraccarichi di specializzazioni di tipo generico.
Su base giornaliera, questa è la differenza che noto di più tra CLR e JVM.
Altre differenze importanti includono:
Il CLR ha chiusure (implementate come delegati C #). JVM supporta le chiusure solo da Java 8.
Il CLR ha coroutine (implementato con la parola chiave "rendimento" C #). La JVM no.
Il CLR consente al codice utente di definire nuovi tipi di valore (strutture), mentre la JVM fornisce una raccolta fissa di tipi di valore (byte, short, int, long, float, double, char, boolean) e consente solo agli utenti di definire nuovi riferimenti- tipi (classi).
Il CLR fornisce supporto per la dichiarazione e la manipolazione di puntatori. Ciò è particolarmente interessante perché sia la JVM che il CLR utilizzano implementazioni rigorose di compattazione di generatori di rifiuti come strategia di gestione della memoria. In circostanze normali, un GC di compattazione rigorosa ha davvero dei problemi con i puntatori, perché quando si sposta un valore da una posizione di memoria a un'altra, tutti i puntatori (e puntatori a puntatori) diventano non validi. Ma il CLR fornisce un meccanismo di "blocco" in modo che gli sviluppatori possano dichiarare un blocco di codice all'interno del quale al CLR non è consentito spostare determinati puntatori. È molto conveniente.
La più grande unità di codice nella JVM è un "pacchetto" come evidenziato dalla parola chiave "protetta" o probabilmente un JAR (ovvero Java ARchive) come evidenziato dalla capacità di specificare un vaso nel percorso di classe e farlo trattare come una cartella di codice. Nel CLR, le classi sono aggregate in "assembly" e il CLR fornisce la logica per ragionare e manipolare gli assembly (che vengono caricati in "AppDomains", fornendo sandbox a livello di applicazione secondaria per l'allocazione della memoria e l'esecuzione del codice).
Il formato bytecode CLR (composto da istruzioni MSIL e metadati) ha meno tipi di istruzioni rispetto alla JVM. Nella JVM, ogni operazione unica (aggiungi due valori int, aggiungi due valori float, ecc.) Ha le sue istruzioni uniche. Nel CLR, tutte le istruzioni MSIL sono polimorfiche (aggiungere due valori) e il compilatore JIT è responsabile della determinazione dei tipi di operandi e della creazione del codice macchina appropriato. Tuttavia, non so quale sia la strategia preferibile. Entrambi hanno dei compromessi. Il compilatore JIT HotSpot, per JVM, può utilizzare un meccanismo di generazione del codice più semplice (non è necessario determinare i tipi di operando, poiché sono già codificati nell'istruzione), ma ciò significa che richiede un formato bytecode più complesso, con più tipi di istruzioni.
Sto usando Java (e ammirando la JVM) da circa dieci anni.
Ma, secondo me, il CLR è ora l'implementazione superiore, in quasi tutti i modi.