Java Virtual Machine (JVM) è un componente principale di Java Runtime Environment (JRE) che consente ai programmi Java di essere eseguiti su qualsiasi piattaforma senza modifiche. JVM funge da interprete tra il bytecode Java e l'hardware sottostante fornendo la famosa funzionalità WORA (Write Once Run Anywhere) di Java.
- Sorgente Java (.java) -> compilato da javac -> bytecode (.class)
- JVM carica il bytecode, verifica che lo colleghi e quindi lo esegue
- L'esecuzione può comportare l'interpretazione del bytecode o l'utilizzo della compilazione Just-In-Time (JIT) per convertire l'hot code in codice macchina nativo per le prestazioni
- La Garbage Collection viene eseguita in background per recuperare memoria dagli oggetti inutilizzati
Architettura della JVM
L'immagine seguente mostra l'architettura e i componenti chiave di JVM.
Componenti dell'architettura JVM
Ora discuteremo in dettaglio ogni componente della JVM.
1. Sottosistema caricatore di classi
È principalmente responsabile di tre attività.
1. Caricamento
- Legge i file .class e memorizza i metadati della classe nell'area dei metodi.
- Crea un oggetto Class nell'heap che rappresenta la classe caricata.
class GFG{ static{ System.out.println('GFG class is loaded by the JVM!'); } public void display(){ System.out.println('Method of GFG class is executed.'); } } public class Test{ public static void main(String[] args) throws Exception{ System.out.println('Main method started.'); // Loading the class explicitly using Class.forName() Class.forName('GFG'); System.out.println('Class loaded successfully.'); // Creating object to execute method GFG obj = new GFG(); obj.display(); } }
Produzione
Main method started. GFG class is loaded by the JVM! Class loaded successfully. Method of GFG class is executed.
Nota: Per ogni carico .classe solo file uno viene creato l'oggetto della classe.
2. Collegamento: Responsabile della preparazione della classe caricata per l'esecuzione. Comprende tre passaggi:
- Verifica: Garantisce che il bytecode segua le regole JVM e sia sicuro da eseguire.
- Preparazione: Alloca memoria per variabili statiche e assegna valori predefiniti.
- Risoluzione: Converte i riferimenti simbolici in riferimenti diretti in memoria.
3. Inizializzazione
- Assegna valori effettivi a variabili statiche.
- Esegue i blocchi statici definiti nella classe.
Tipi di caricatore di classi
- Caricatore di classi Bootstrap: Carica le classi Java principali (JAVA_HOME/lib).
- Caricatore di classi di estensione: Carica le classi dalla directory delle estensioni (JAVA_HOME/jre/lib/ext).
- Caricatore classi di sistema/applicazione: Carica le classi dal percorso classi dell'applicazione.
// Java code to demonstrate Class Loader subsystem public class Geeks { public static void main(String[] args) { // String class is loaded by bootstrap loader and // bootstrap loader is not Java object hence null System.out.println(String.class.getClassLoader()); // Test class is loaded by Application loader System.out.println(Geeks.class.getClassLoader()); } }
Produzione
null jdk.internal.loader.ClassLoaders$AppClassLoader@8bcc55f
2. Aree di memoria JVM
- Area del metodo: Memorizza informazioni a livello di classe come nome della classe, metodi della classe genitore, variabili e dati statici. Condiviso attraverso la JVM.
- Area dell'heap: Memorizza tutti gli oggetti. Condiviso attraverso la JVM.
- Area della pila: Ogni thread ha il proprio stack di runtime; Il metodo Store chiama variabili locali negli stack frame. Distrutto quando il thread finisce.
- Registri PC: Conserva l'indirizzo dell'istruzione attualmente in esecuzione per ciascun thread.
- Stack di metodi nativi: Ogni thread ha uno stack separato per l'esecuzione del metodo nativo.
3. Motore di esecuzione
Il motore di esecuzione esegue il file .class (bytecode). Legge il byte-code riga per riga, utilizza dati e informazioni presenti in varie aree di memoria ed esegue istruzioni. Può essere classificato in tre parti:
- Interprete: Interpreta il bytecode riga per riga e quindi lo esegue. Lo svantaggio qui è che quando un metodo viene chiamato più volte ogni volta è richiesta l'interpretazione.
- Compilatore Just-In-Time (JIT): Viene utilizzato per aumentare l'efficienza di un interprete. Compila l'intero bytecode e lo modifica in codice nativo, quindi ogni volta che l'interprete vede chiamate di metodo ripetute, JIT fornisce codice nativo diretto per quella parte, quindi non è necessaria la reinterpretazione, quindi l'efficienza viene migliorata.
- Raccoglitore di rifiuti: Distrugge gli oggetti senza riferimenti. Per ulteriori informazioni su Garbage Collector fare riferimento Raccoglitore di rifiuti .
4. Interfaccia nativa Java (JNI)
È un'interfaccia che interagisce con le librerie di metodi nativi e fornisce le librerie native (C C++) necessarie per l'esecuzione. Consente alla JVM di chiamare librerie C/C++ e di essere chiamata da librerie C/C++ che potrebbero essere specifiche dell'hardware.
5. Librerie di metodi nativi
Si tratta di raccolte di librerie native necessarie per l'esecuzione di metodi nativi. Includono librerie scritte in linguaggi come C e C++.