JavaRush /Java Blog /Random-IT /Compilazione ed esecuzione di applicazioni Java dietro le...
Павел Голов
Livello 34
Москва

Compilazione ed esecuzione di applicazioni Java dietro le quinte

Pubblicato nel gruppo Random-IT

Contenuto:

  1. introduzione
  2. Compilazione in bytecode
  3. Esempio di compilazione ed esecuzione del programma
  4. Esecuzione di un programma su una macchina virtuale
  5. Compilazione just-in-time (JIT).
  6. Conclusione
Compilazione ed esecuzione di applicazioni Java dietro le quinte - 1

1. Introduzione

Ciao a tutti! Oggi vorrei condividere la conoscenza di ciò che accade sotto il cofano della JVM (Java Virtual Machine) dopo aver eseguito un'applicazione scritta Java. Al giorno d'oggi, esistono ambienti di sviluppo alla moda che ti aiutano a evitare di pensare agli interni della JVM, alla compilazione e all'esecuzione del codice Java, il che può far sì che i nuovi sviluppatori trascurino questi aspetti importanti. Allo stesso tempo, durante le interviste vengono spesso poste domande su questo argomento, motivo per cui ho deciso di scrivere un articolo.

2. Compilazione in bytecode

Compilazione ed esecuzione di applicazioni Java dietro le quinte - 2
Cominciamo con la teoria. Quando scriviamo qualsiasi applicazione, creiamo un file con un'estensione .javae inseriamo al suo interno il codice nel linguaggio di programmazione Java. Un file di questo tipo contenente codice leggibile dall'uomo è chiamato file del codice sorgente . Una volta che il file del codice sorgente è pronto, devi eseguirlo! Ma allo stadio contiene informazioni comprensibili solo agli esseri umani. Java è un linguaggio di programmazione multipiattaforma. Ciò significa che i programmi scritti in Java possono essere eseguiti su qualsiasi piattaforma su cui sia installato un sistema runtime Java dedicato. Questo sistema si chiama Java Virtual Machine (JVM). Per tradurre un programma dal codice sorgente in codice comprensibile dalla JVM, è necessario compilarlo. Il codice che la JVM capisce si chiama bytecode e contiene una serie di istruzioni che la macchina virtuale eseguirà successivamente. Per compilare il codice sorgente in bytecode, c'è un compilatore javacincluso nel JDK (Java Development Kit). Come input, il compilatore accetta un file con estensione .java, contenente il codice sorgente del programma, e come output produce un file con estensione .class, contenente il bytecode necessario affinché il programma possa essere eseguito dalla macchina virtuale. Una volta che un programma è stato compilato in bytecode, può essere eseguito utilizzando una macchina virtuale.

3. Esempio di compilazione ed esecuzione del programma

Supponiamo di avere un semplice programma, contenuto in un file Calculator.java, che accetta 2 argomenti numerici da riga di comando e stampa il risultato della loro somma:
class Calculator {
    public static void main(String[] args){
        int a = Integer.valueOf(args[0]);
        int b = Integer.valueOf(args[1]);

        System.out.println(a + b);
    }
}
Per compilare questo programma in bytecode, utilizzeremo il compilatore javacsulla riga di comando:
javac Calculator.java
Dopo la compilazione, riceviamo come output un file con bytecode Calculator.class, che possiamo eseguire utilizzando la macchina java installata sul nostro computer utilizzando il comando java sulla riga di comando:
java Calculator 1 2
Tieni presente che dopo il nome del file sono stati specificati 2 argomenti della riga di comando: i numeri 1 e 2. Dopo aver eseguito il programma, sulla riga di comando verrà visualizzato il numero 3. Nell'esempio sopra, avevamo una classe semplice che vive da sola . Ma cosa succede se la lezione è in qualche pacchetto? Simuliamo la seguente situazione: creiamo delle directory src/ru/javarushe posizioniamo lì la nostra classe. Ora appare così (abbiamo aggiunto il nome del pacchetto all'inizio del file):
package ru.javarush;

class Calculator {
    public static void main(String[] args){
        int a = Integer.valueOf(args[0]);
        int b = Integer.valueOf(args[1]);

        System.out.println(a + b);
    }
}
Compiliamo una classe di questo tipo con il seguente comando:
javac -d bin src/ru/javarush/Calculator.java
In questo esempio abbiamo utilizzato un'opzione aggiuntiva del compilatore -d binche inserisce i file compilati in una directory bincon una struttura simile alla directory src, ma la directory bindeve essere creata in anticipo. Questa tecnica viene utilizzata per evitare di confondere i file di codice sorgente con i file bytecode. Prima di eseguire il programma compilato è opportuno spiegare il concetto classpath. Classpathè il percorso rispetto al quale la macchina virtuale cercherà i pacchetti e le classi compilate. Cioè, in questo modo diciamo alla macchina virtuale quali directory nel file system sono la radice della gerarchia dei pacchetti Java. Classpathpuò essere specificato all'avvio del programma utilizzando il flag -classpath. Lanciamo il programma utilizzando il comando:
java -classpath ./bin ru.javarush.Calculator 1 2
In questo esempio abbiamo richiesto il nome completo della classe, incluso il nome del pacchetto in cui risiede. L'albero dei file finale è simile al seguente:
├── src
│     └── ru
│          └── javarush
│                  └── Calculator.java
└── bin
      └── ru
           └── javarush
                   └── Calculator.class

4. Esecuzione del programma da parte di una macchina virtuale

Quindi, abbiamo lanciato il programma scritto. Ma cosa succede quando un programma compilato viene lanciato da una macchina virtuale? Per prima cosa, vediamo cosa significano i concetti di compilazione e interpretazione del codice. La compilazione è la traduzione di un programma scritto in un linguaggio sorgente di alto livello in un programma equivalente in un linguaggio di basso livello simile al codice macchina. L'interpretazione è un'analisi operatore per istruzione (comando per riga, riga per riga), elaborazione ed esecuzione immediata del programma sorgente o della richiesta (al contrario della compilazione, in cui il programma viene tradotto senza eseguirlo). Il linguaggio Java ha sia un compilatore ( javac) che un interprete, che è una macchina virtuale che converte il bytecode in codice macchina riga per riga e lo esegue immediatamente. Pertanto, quando eseguiamo un programma compilato, la macchina virtuale inizia a interpretarlo, ovvero a convertire riga per riga il bytecode in codice macchina, nonché la sua esecuzione. Sfortunatamente, la pura interpretazione del bytecode è un processo piuttosto lungo e rende Java lento rispetto ai suoi concorrenti. Per evitare ciò è stato introdotto un meccanismo per velocizzare l'interpretazione del bytecode da parte della macchina virtuale. Questo meccanismo è chiamato compilazione Just-in-time (JITC).

5. Compilazione just-in-time (JIT).

In termini semplici, il meccanismo della compilazione Just-In-Time è il seguente: se nel programma ci sono parti del codice che vengono eseguite più volte, possono essere compilate una volta nel codice macchina per accelerarne l'esecuzione in futuro. Dopo aver compilato tale parte del programma in codice macchina, ad ogni successiva chiamata a questa parte del programma, la macchina virtuale eseguirà immediatamente il codice macchina compilato anziché interpretarlo, il che ovviamente accelererà l'esecuzione del programma. L'accelerazione del programma si ottiene aumentando il consumo di memoria (dobbiamo memorizzare il codice macchina compilato da qualche parte!) e aumentando il tempo impiegato nella compilazione durante l'esecuzione del programma. La compilazione JIT è un meccanismo piuttosto complesso, quindi andiamo oltre. Esistono 4 livelli di compilazione JIT del bytecode nel codice macchina. Più alto è il livello di compilazione, più complessa sarà, ma allo stesso tempo l'esecuzione di una sezione del genere sarà più veloce di una sezione con livello inferiore. JIT: il compilatore decide quale livello di compilazione impostare per ciascun frammento di programma in base alla frequenza con cui viene eseguito quel frammento. Sotto il cofano, la JVM utilizza 2 compilatori JIT: C1 e C2. Il compilatore C1 è anche chiamato compilatore client ed è in grado di compilare codice solo fino al 3° livello. Il compilatore C2 è responsabile del 4° livello di compilazione, il più complesso e veloce.
Compilazione ed esecuzione di applicazioni Java dietro le quinte - 3
Da quanto sopra possiamo concludere che per semplici applicazioni client è più vantaggioso utilizzare il compilatore C1, poiché in questo caso è importante per noi la velocità con cui si avvia l'applicazione. Le applicazioni di lunga durata lato server possono richiedere più tempo per avviarsi, ma in futuro dovranno funzionare ed eseguire la loro funzione rapidamente: qui il compilatore C2 è adatto a noi. Quando si esegue un programma Java sulla versione x32 della JVM, possiamo specificare manualmente quale modalità vogliamo utilizzare utilizzando i flag -cliente -server. Quando viene specificato questo flag, -clientla JVM non eseguirà complesse ottimizzazioni del bytecode, il che accelererà il tempo di avvio dell'applicazione e ridurrà la quantità di memoria consumata. Quando si specifica il flag, -serverl'applicazione impiegherà più tempo ad avviarsi a causa delle complesse ottimizzazioni del bytecode e utilizzerà più memoria per archiviare il codice macchina, ma il programma verrà eseguito più velocemente in futuro. Nella versione x64 della JVM, il flag -clientviene ignorato e la configurazione del server delle applicazioni viene utilizzata per impostazione predefinita.

6. Conclusione

Con questo si conclude la mia breve panoramica su come funziona la compilazione e l'esecuzione di un'applicazione Java. Punti principali:
  1. Il compilatore javac converte il codice sorgente di un programma in bytecode che può essere eseguito su qualsiasi piattaforma su cui è installata la Java virtual machine;
  2. Dopo la compilazione, la JVM interpreta il bytecode risultante;
  3. Per velocizzare le applicazioni Java, la JVM utilizza un meccanismo di compilazione Just-In-Time che converte le sezioni di un programma eseguite più frequentemente in codice macchina e le archivia in memoria.
Spero che questo articolo ti abbia aiutato a comprendere più a fondo come funziona il nostro linguaggio di programmazione preferito. Grazie per aver letto, le critiche sono benvenute!
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION