Nota:
- Questa esercitazione è disponibile in un ambiente di laboratorio gratuito fornito da Oracle.
- Utilizza valori di esempio per le credenziali, la tenancy e i compartimenti Oracle Cloud Infrastructure. Al termine del laboratorio, sostituire questi valori con quelli specifici del tuo ambiente cloud.
Informazioni sulla riflessione e sull'immagine nativa GraalVM
Introduzione
Questo laboratorio è dedicato agli sviluppatori che desiderano saperne di più sul funzionamento delle riflessioni nell'immagine nativa di GraalVM.
GraalVM Native Image consente la compilazione precedente di un'applicazione Java in un eseguibile auto-contenuto nativo. Con l'immagine nativa GraalVM, solo il codice richiesto dall'applicazione in fase di esecuzione viene aggiunto all'eseguibile nativo.
Questi eseguibili nativi hanno una serie di vantaggi importanti, in quanto:
- Utilizzare una frazione delle risorse richieste dalla JVM, per un'esecuzione più economica
- Inizia in millisecondi
- Garantisci immediatamente prestazioni di picco, senza riscaldamento
- Possibilità di imballare in immagini container leggere per distribuzioni più rapide ed efficienti
- Superficie di attacco ridotta (più su questo in futuri laboratori)
Molti dei principali framework di microservizi supportano la compilazione in anticipo con l'immagine nativa GraalVM, tra cui Micronaut, Spring, Helidon e Quarkus.
Inoltre, esistono plugin Maven e Gradle per l'immagine nativa per semplificare la creazione, il test e l'esecuzione di applicazioni Java come eseguibili nativi.
Nota: Oracle Cloud Infrastructure (OCI) offre GraalVM Enterprise senza costi aggiuntivi.
Tempo di laboratorio stimato: 30 minuti
Obiettivi laboratorio
In questo laboratorio verranno eseguiti i seguenti task:
- Scopri come creare codice Java che utilizza la riflessione negli eseguibili standalone, utilizzando lo strumento di creazione
native-image
- Scopri gli strumenti di configurazione assistiti disponibili con GraalVM
NOTA: ogni volta che viene visualizzata l'icona del laptop, è necessario eseguire le operazioni desiderate. Guardate questi.
# This is where we you will need to do something
L'ambiente di sviluppo viene fornito da un host remoto: un'istanza di computazione OCI con Oracle Linux 8, 1 CPU e 32 GB di memoria.
L'ambiente desktop Luna Labs verrà visualizzato prima che l'host remoto sia pronto, che può richiedere fino a due minuti.
STEP 1: connessione a un host virtuale e controllo dell'ambiente di sviluppo
La connessione all'host remoto viene eseguita mediante l'esecuzione di uno script di installazione nell'ambiente desktop Luna. Questo script è disponibile tramite la scheda Risorse
-
Nel desktop fare doppio clic sull'icona Luna-Lab.html. Viene visualizzata la pagina per visualizzare le credenziali e le informazioni di Oracle Cloud Infrastructure specifiche del laboratorio.
-
Verrà visualizzata la scheda Risorse. Tenere presente che la struttura visualizzata accanto al titolo Risorse verrà attivata mentre viene eseguito il provisioning dell'istanza di computazione nel cloud teh.
-
Quando viene eseguito il provisioning dell'istanza, l'operazione potrebbe richiedere fino a 2 minuti, nella scheda Risorse verrà visualizzato quanto segue.
-
Copiare lo script di configurazione, che imposta l'ambiente Codice VS dalla scheda Risorse. Fare clic sul collegamento Visualizza dettagli per visualizzare la configurazione. Copiare questo file come mostrato nella schermata qui sotto.
-
Aprire un terminale, come mostrato nello screenshot seguente:
-
Incollare il codice di configurazione nel terminale, che aprirà il codice VS.
E tu sei fatto! Congratulazioni. La connessione a un host remoto in Oracle Cloud è stata completata.
STEP 2: il presupposto mondiale chiuso
Creare un eseguibile standalone con lo strumento native-image
fornito con GraalVM è un po' diverso dalla creazione di applicazioni Java. L'immagine nativa utilizza ciò che è noto come presupposto del mondo chiuso.
L'ipotesi del mondo chiuso indica che tutto il codice bytecode nell'applicazione che può essere chiamato in fase di esecuzione deve essere noto (osservato e analizzato) in fase di creazione, ovvero quando lo strumento native-image
sta creando l'eseguibile standalone.
Prima di continuare, vale la pena seguire il modello di creazione/esecuzione per le applicazioni create con l'immagine nativa GraalVM.
- Compila il tuo codice sorgente Java in classi di codici byte Java
- Utilizzando lo strumento
native-image
, creare tali classi di codici byte Java in un eseguibile nativo - Esegui eseguibile nativo
Ma, cosa accade davvero al passo 2?
In primo luogo, lo strumento native-image
esegue un'analisi per vedere quali classi all'interno dell'applicazione sono raggiungibili. A breve ne analizzeremo più in dettaglio.
In secondo luogo, vengono inizializzate le classi notoriamente da inizializzare in sicurezza (inizializzazione automatica delle classi sicure). I dati di classe delle classi inizializzate vengono caricati nell'heap dell'immagine, che a sua volta viene salvato nell'eseguibile standalone (nella sezione di testo). Questa è una delle caratteristiche dello strumento GraalVM native-image
che può creare applicazioni di avvio così rapido.
NOTA: non corrisponde all'inizializzazione dell'oggetto. L'inizializzazione dell'oggetto si verifica durante il runtime dell'eseguibile nativo.
Abbiamo detto che torneremo al tema della portata. Come indicato in precedenza, l'analisi determina quali classi, metodi e campi devono essere inclusi nell'eseguibile standalone. L'analisi è statica, ovvero non esegue il codice. L'analisi può determinare alcuni casi di caricamento e utilizzo dinamico delle classi (vedere ), ma ci sono casi che non sarà in grado di riprendere.
Per gestire le funzioni dinamiche di Java, è necessario fornire informazioni sull'utilizzo della riflessione delle classi o sulle classi caricate in modo dinamico.
Di seguito è riportato un esempio.
STEP 3: esempio utilizzando la selezione
Si supponga di disporre della seguente classe, ReflectionExample.java
(una copia di questa è disponibile nella directory, demo/ReflectionExample.java
):
import java.lang.reflect.Method;
class StringReverser {
static String reverse(String input) {
return new StringBuilder(input).reverse().toString();
}
}
class StringCapitalizer {
static String capitalize(String input) {
return input.toUpperCase();
}
}
public class ReflectionExample {
public static void main(String[] args) throws ReflectiveOperationException {
String className = args[0];
String methodName = args[1];
String input = args[2];
Class<?> clazz = Class.forName(className);
Method method = clazz.getDeclaredMethod(methodName, String.class);
Object result = method.invoke(null, input);
System.out.println(result);
}
}
In primo luogo, creare un terminale all'interno del codice VS. Questa operazione viene eseguita da Terminal > New Terminal
Successivamente, creiamo il codice. Nella shell, dall'interno del codice VS, eseguire il seguente comando:
javac ReflectionExample.java
Il metodo principale nella classe ReflectionExample
carica una classe il cui nome è stato passato come argomento, un caso d'uso molto dinamico! Il secondo argomento della classe è il nome del metodo della classe caricata dinamicamente da richiamare.
Facciamola e vediamo cosa fa.
java ReflectionExample StringReverser reverse "hello"
Come ci aspettavamo, il metodo reverse
sulla classe StringReverser
è stato trovato, attraverso riflessione. Il metodo è stato invocato e ha invertito il nostro input String di "hello". Fino ad ora, così bene.
OK, ma cosa succede se cerchiamo di creare un'immagine nativa fuori dal programma? Proviamo. Nella shell eseguire il comando seguente:
native-image --no-fallback ReflectionExample
NOTA: l'opzione
--no-fallback
sunative-image
causa l'errore della build se non è in grado di creare un'eseguutabale nativo standalone.
Ora eseguiamo l'eseguibile nativo generato e vediamo cosa fa:
./reflectionexample StringReverser reverse "hello"
Exception in thread "main" java.lang.ClassNotFoundException: StringReverser
at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:60)
at java.lang.Class.forName(DynamicHub.java:1214)
at ReflectionExample.main(ReflectionExample.java:21)
Che cosa è successo qui? Sembra che il nostro eseguibile nativo non sia stato in grado di trovare la classe, StringReverser
. Come è successo? A questo punto, probabilmente abbiamo un ’ idea del perché. Il presupposto del mondo chiuso.
Durante l'analisi eseguita dallo strumento native-image
, non è stato possibile determinare l'utilizzo della classe StringReverser
. Pertanto ha rimosso la classe dall'eseguibile nativo generato. Nota: rimuovendo le classi indesiderate dall'eseguibile standalone, lo strumento per ridurre il codice creato includendo solo le classi notoriamente utilizzate. Come abbiamo appena visto, la riflessione può riguardare temi, ma fortunatamente c ’ è un modo per affrontarli.
STEP 4: introduzione della configurazione di riflessione delle immagini native
Possiamo fornire allo strumento di creazione native-image
informazioni sulle istanze di riflessione mediante file di configurazione speciali. Questi file vengono scritti in JSON
e possono essere passati allo strumento native-image
mediante l'uso di flag.
Quindi, quali altri tipi di informazioni di configurazione possiamo passare allo strumento di creazione native-image
? Lo strumento attualmente supporta file di lettura contenenti dettagli su:
- Riflessione
- Risorse: file di risorse che saranno richiesti dall'applicazione.
- JNI
- Proxy dinamici
- Serializzazione
Stiamo solo esaminando le modalità di riflessione in questo laboratorio, per cui ci concentreremo su questo aspetto.
Di seguito è riportato un esempio dell'aspetto di questi file (tratto da qui):
[
{
"name" : "java.lang.Class",
"queryAllDeclaredConstructors" : true,
"queryAllPublicConstructors" : true,
"queryAllDeclaredMethods" : true,
"queryAllPublicMethods" : true,
"allDeclaredClasses" : true,
"allPublicClasses" : true
},
{
"name" : "java.lang.String",
"fields" : [
{ "name" : "value" },
{ "name" : "hash" }
],
"methods" : [
{ "name" : "<init>", "parameterTypes" : [] },
{ "name" : "<init>", "parameterTypes" : ["char[]"] },
{ "name" : "charAt" },
{ "name" : "format", "parameterTypes" : ["java.lang.String", "java.lang.Object[]"] }
]
},
{
"name" : "java.lang.String$CaseInsensitiveComparator",
"queriedMethods" : [
{ "name" : "compare" }
]
}
]
Da questo è possibile osservare che le classi e i metodi a cui si accede tramite l'API di riflessione devono essere configurati. È possibile eseguire questa operazione manualmente, ma il modo più conveniente per generare questi file di configurazione è l'uso della configurazione assistita javaagent
.
STEP 5: immagine nativa, configurazione assistita: immettere l'agente Java
La scrittura di un file di configurazione di riflessione completo da zero è certamente possibile, ma il runtime Java di GraalVM fornisce un agente di trace java, il file javaagent
, che ne genererà automaticamente quando si esegue l'applicazione.
proviamo questo.
Eseguire l'applicazione con l'agente di trace abilitato. Nella nostra shell eseguire:
# Note: the tracing agent parameter must come before the classpath and jar params on the command ine
java -agentlib:native-image-agent=config-output-dir=META-INF/native-image ReflectionExample StringReverser reverse "hello"
Di seguito viene riportata la configurazione creata.
cat META-INF/native-image/reflect-config.json
[
{
"name":"StringReverser",
"methods":[{"name":"reverse","parameterTypes":["java.lang.String"] }]
}
]
È possibile eseguire questo processo per due volte e le esecuzioni vengono unite se si specifica native-image-agent=config-merge-dir
, come mostrato nell'esempio riportato di seguito.
java -agentlib:native-image-agent=config-merge-dir=META-INF/native-image ReflectionExample StringCapitalizer capitalize "hello"
La creazione dell'eseguibile standalone ora utilizzerà la configurazione fornita. Costruiamolo:
native-image --no-fallback ReflectionExample
E vediamo se funziona meglio:
./reflectionexample StringReverser reverse "hello"
È così!
Conclusioni
La creazione di eseguibili standalone con l'immagine nativa GraalVM si basa sull'ipotesi Chiuso nel mondo, che è necessario conoscere in anticipo, quando si creano eseguibili standalone, su qualsiasi caso di riflessione che può verificarsi nel nostro codice.
La piattaforma GraalVM consente di specificare lo strumento di creazione native-image
quando viene utilizzata la riflessione. Nota: per alcuni casi semplici, lo strumento native-image
può trovarli per se stesso.
La piattaforma GraalVM offre anche un modo per scoprire gli utilizzi di riflessioni e altri comportamenti dinamici tramite l'agente di trace Java e può generare automaticamente i file di configurazione necessari dallo strumento native-image
.
Ci sono alcune cose che si dovrebbe tenere presente quando si utilizza l'agente di rintracciamento:
- Utilizzare le suite di test. È necessario esercitare quanti percorsi nel codice è possibile
- Potrebbe essere necessario rivedere e modificare i file di configurazione
Ci auguriamo che l'esercitazione sia stata apprezzata e che l'utente abbia imparato qualcosa su come gestire la riflessione quando utilizza l'immagine nativa.
Per saperne di più
- Guarda una presentazione dell'architetto di immagini native Christian Wimmer Immagine nativa GraalVM: Analisi statica su larga scala per Java
- Documentazione di riferimento per l'immagine nativa GraalVM EE
- Uso della riflessione nelle immagini native
- Inizializzazione classe nell'immagine nativa
- Configurazione assistita con agente di trace
Altre risorse di apprendimento
Esplora altri laboratori su docs.oracle.com/learn o accedi a più contenuti di apprendimento gratuito sul canale Oracle Learning YouTube. Inoltre, visitare education.oracle.com/learning-explorer per diventare Oracle Learning Explorer.
Per la documentazione del prodotto, visitare il sito Oracle Help Center.
Understanding Reflection and GraalVM Native Image
F54901-01
March 2022
Copyright © 2022, Oracle and/or its affiliates.