Utilizzo della memoria virtuale da Java sotto Linux, troppa memoria utilizzata


259

Ho un problema con un'applicazione Java in esecuzione su Linux.

Quando avvio l'applicazione, utilizzando la dimensione heap massima predefinita (64 MB), vedo utilizzando l'applicazione tops che 240 MB di memoria virtuale vengono assegnati all'applicazione. Ciò crea alcuni problemi con alcuni altri software sul computer, che è relativamente limitato alle risorse.

La memoria virtuale riservata non verrà comunque utilizzata, per quanto ho capito, perché una volta raggiunto il limite di heap OutOfMemoryErrorviene lanciata una. Ho eseguito la stessa applicazione in Windows e vedo che la dimensione della memoria virtuale e la dimensione dell'heap sono simili.

È possibile configurare la memoria virtuale in uso per un processo Java su Linux?

Modifica 1 : il problema non è l'heap. Il problema è che se imposto un Heap di 128 MB, ad esempio, Linux alloca ancora 210 MB di memoria virtuale, che non è mai necessaria, **.

Modifica 2 : L'uso ulimit -vconsente di limitare la quantità di memoria virtuale. Se la dimensione impostata è inferiore a 204 MB, l'applicazione non verrà eseguita anche se non richiede 204 MB, solo 64 MB. Quindi voglio capire perché Java richiede così tanta memoria virtuale. Questo può essere cambiato?

Modifica 3 : ci sono molte altre applicazioni in esecuzione nel sistema, che è incorporato. E il sistema ha un limite di memoria virtuale (dai commenti, dettagli importanti).


Perché sei interessato all'utilizzo della memoria virtuale? Se vuoi davvero essere preoccupato, osserva l'utilizzo della memoria residente e leggi i seguenti comandi: free, ps, top.
Basszero,

2
Esistono diverse altre applicazioni in esecuzione nel sistema, che è incorporato. E il sistema ha un limite di memoria virtuale.
Mario Ortegón,

ahhhh, il diavolo è nei dettagli
basszero,

Quale implementazione di Java stai usando. IIRC, lo standard gratuito (non OpenJDK) senza JOG Sun non è concesso in licenza per l'uso incorporato.
Tom Hawtin - tackline il

Penso di aver perso la parte "incorporata" ... è limitata la memoria e l'hardware è personalizzato, ma è ancora un computer standard
Mario Ortegón,

Risposte:


630

Questa è stata una lamentela di vecchia data con Java, ma è in gran parte insignificante e di solito basata sulla ricerca di informazioni sbagliate. Il solito fraseggio è qualcosa del tipo "Hello World su Java richiede 10 megabyte! Perché ne ha bisogno?" Bene, ecco un modo per rendere Hello World su una richiesta JVM a 64 bit di assumere oltre 4 gigabyte ... almeno con una forma di misurazione.

java -Xms1024m -Xmx4096m com.example.Hello

Diversi modi per misurare la memoria

Su Linux, il comando principale fornisce diversi numeri per la memoria. Ecco cosa dice dell'esempio Hello World:

  PID UTENTE PR NI VIRT RES SHR S% CPU% MEM TIME + COMANDO
 2120 kg registrati 20 0 4373m 15m 7152 S 0 0,2 0: 00.10 java
  • VIRT è lo spazio di memoria virtuale: la somma di tutto nella mappa di memoria virtuale (vedi sotto). È in gran parte insignificante, tranne quando non lo è (vedi sotto).
  • RES è la dimensione del set residente: il numero di pagine che sono attualmente residenti nella RAM. In quasi tutti i casi, questo è l'unico numero che dovresti usare quando dici "troppo grande". Ma non è ancora un numero molto buono, soprattutto quando si parla di Java.
  • SHR è la quantità di memoria residente condivisa con altri processi. Per un processo Java, questo è in genere limitato alle librerie condivise e ai file JAR associati alla memoria. In questo esempio, avevo solo un processo Java in esecuzione, quindi sospetto che il 7k sia il risultato delle librerie utilizzate dal sistema operativo.
  • SWAP non è attivato per impostazione predefinita e non è mostrato qui. Indica la quantità di memoria virtuale attualmente residente sul disco, indipendentemente dal fatto che sia effettivamente nello spazio di swap . Il sistema operativo è molto bravo a mantenere le pagine attive nella RAM e le uniche cure per lo scambio sono (1) acquistare più memoria o (2) ridurre il numero di processi, quindi è meglio ignorare questo numero.

La situazione per Task Manager di Windows è un po 'più complicata. In Windows XP, ci sono le colonne "Uso memoria" e "Dimensione memoria virtuale", ma la documentazione ufficiale non contiene informazioni sul loro significato. Windows Vista e Windows 7 aggiungono più colonne e sono effettivamente documentate . Di questi, la misura "Working Set" è la più utile; corrisponde approssimativamente alla somma di RES e SHR su Linux.

Comprensione della mappa della memoria virtuale

La memoria virtuale consumata da un processo è il totale di tutto ciò che è presente nella mappa della memoria di processo. Ciò include i dati (ad esempio, l'heap Java), ma anche tutte le librerie condivise e i file mappati in memoria utilizzati dal programma. Su Linux, puoi usare il comando pmap per vedere tutte le cose mappate nello spazio del processo (da qui in poi farò riferimento a Linux, perché è quello che uso; sono sicuro che ci sono strumenti equivalenti per Finestre). Ecco un estratto dalla mappa di memoria del programma "Hello World"; l'intera mappa di memoria è lunga oltre 100 righe e non è insolito avere un elenco di mille righe.

0000000040000000 36K rx-- /usr/local/java/jdk-1.6-x64/bin/java
0000000040108000 8K rwx-- /usr/local/java/jdk-1.6-x64/bin/java
0000000040eba000 676K rwx-- [anon]
00000006fae00000 21248K rwx-- [anon]
00000006fc2c0000 62720K rwx-- [anon]
0000000700000000 699072K rwx-- [anon]
000000072aab0000 2097152K rwx-- [anon]
00000007aaab0000 349504K rwx-- [anon]
00000007c0000000 1048576K rwx-- [anon]
...
00007fa1ed00d000 1652K r-xs- /usr/local/java/jdk-1.6-x64/jre/lib/rt.jar
...
00007fa1ed1d3000 1024K rwx-- [anon]
00007fa1ed2d3000 4K ----- [anon]
00007fa1ed2d4000 1024K rwx-- [anon]
00007fa1ed3d4000 4K ----- [anon]
...
00007fa1f20d3000 164K rx-- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f20fc000 1020K ----- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f21fb000 28K rwx-- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
...
00007fa1f34aa000 1576K rx-- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3634000 2044K ----- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3833000 16K rx-- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3837000 4K rwx-- /lib/x86_64-linux-gnu/libc-2.13.so
...

Una rapida spiegazione del formato: ogni riga inizia con l'indirizzo di memoria virtuale del segmento. Segue la dimensione del segmento, le autorizzazioni e l'origine del segmento. Quest'ultimo elemento è un file o "anon", che indica un blocco di memoria allocato tramite mmap .

A partire dall'alto, abbiamo

  • Il caricatore JVM (ovvero il programma che viene eseguito durante la digitazione java). Questo è molto piccolo; tutto ciò che fa è caricare nelle librerie condivise in cui è archiviato il codice JVM reale.
  • Un gruppo di anon blocchi che contengono l'heap Java e i dati interni. Questa è una Sun JVM, quindi l'heap è suddiviso in più generazioni, ognuna delle quali è il proprio blocco di memoria. Si noti che JVM alloca lo spazio di memoria virtuale in base al -Xmxvalore; questo gli consente di avere un heap contiguo. Il -Xmsvalore viene utilizzato internamente per indicare quanta parte dell'heap è "in uso" all'avvio del programma e per attivare la garbage collection all'avvicinarsi di tale limite.
  • Un file JAR mappato in memoria, in questo caso il file che contiene le "classi JDK". Quando si mappa in memoria un JAR, è possibile accedere ai file al suo interno in modo molto efficiente (anziché leggerlo ogni volta dall'inizio). Sun JVM mapperà in memoria tutti i JAR sul percorso di classe; se il codice dell'applicazione deve accedere a un JAR, è anche possibile mapparlo in memoria.
  • Dati per thread per due thread. Il blocco 1M è lo stack di thread. Non avevo una buona spiegazione per il blocco 4k, ma @ericsoe lo ha identificato come un "blocco di protezione": non ha permessi di lettura / scrittura, quindi se si accede causerà un errore di segmento e la JVM lo rileva e lo traduce esso a StackOverFlowError. Per un'app reale, vedrai dozzine se non centinaia di queste voci ripetute attraverso la mappa di memoria.
  • Una delle librerie condivise che contiene l'attuale codice JVM. Ce ne sono diversi.
  • La libreria condivisa per la libreria standard C. Questa è solo una delle tante cose caricate da JVM che non fanno parte strettamente di Java.

Le librerie condivise sono particolarmente interessanti: ogni libreria condivisa ha almeno due segmenti: un segmento di sola lettura contenente il codice della libreria e un segmento di lettura / scrittura che contiene dati globali per processo per la libreria (non so quale sia il segmento senza permessi è; l'ho visto solo su Linux x64). La parte di sola lettura della libreria può essere condivisa tra tutti i processi che utilizzano la libreria; ad esempio, libcha 1,5 M di spazio di memoria virtuale che può essere condiviso.

Quando è importante la dimensione della memoria virtuale?

La mappa della memoria virtuale contiene molte cose. Alcuni di essi sono di sola lettura, alcuni sono condivisi e alcuni sono allocati ma mai toccati (ad esempio, quasi tutti i 4 GB di heap in questo esempio). Ma il sistema operativo è abbastanza intelligente da caricare solo ciò di cui ha bisogno, quindi la dimensione della memoria virtuale è in gran parte irrilevante.

La dimensione della memoria virtuale è importante se si esegue un sistema operativo a 32 bit, dove è possibile allocare solo 2 Gb (o, in alcuni casi, 3Gb) di spazio degli indirizzi di processo. In tal caso, hai a che fare con una risorsa scarsa e potresti dover fare dei compromessi, come ridurre le dimensioni dell'heap per mappare in memoria un file di grandi dimensioni o creare molti thread.

Ma, dato che le macchine a 64 bit sono onnipresenti, non credo che passerà molto tempo prima che la dimensione della memoria virtuale sia una statistica completamente irrilevante.

Quando è importante la dimensione del set di residenti?

La dimensione del set di residenti è quella parte dello spazio di memoria virtuale effettivamente presente nella RAM. Se il tuo RSS diventa una porzione significativa della tua memoria fisica totale, potrebbe essere il momento di iniziare a preoccuparti. Se il tuo RSS cresce per occupare tutta la tua memoria fisica e il tuo sistema inizia a scambiarsi, è molto tempo che inizi a preoccuparti.

Ma RSS è anche fuorviante, specialmente su una macchina leggermente caricata. Il sistema operativo non fa molti sforzi per recuperare le pagine utilizzate da un processo. In tal modo, si ottengono pochi vantaggi e il potenziale per un errore di pagina costoso se il processo tocca la pagina in futuro. Di conseguenza, la statistica RSS può includere molte pagine che non sono in uso attivo.

Linea di fondo

A meno che tu non stia scambiando, non preoccuparti eccessivamente di ciò che ti dicono le varie statistiche sulla memoria. Con l'avvertenza che un RSS in continua crescita può indicare una sorta di perdita di memoria.

Con un programma Java, è molto più importante prestare attenzione a ciò che sta accadendo nell'heap. La quantità totale di spazio consumato è importante e ci sono alcuni passaggi che è possibile adottare per ridurlo. Più importante è la quantità di tempo che passi nella raccolta dei rifiuti e quali parti dell'heap vengono raccolte.

L'accesso al disco (ovvero un database) è costoso e la memoria è economica. Se puoi scambiare l'uno con l'altro, fallo.


9
È necessario tenere conto del fatto che parti della memoria attualmente scambiate mancano dalla misura RES. Quindi potresti avere un valore RES basso ma solo perché l'applicazione era inattiva e gran parte dell'heap è stato scambiato su disco. Java fa un pessimo lavoro da scambiare: su ogni GC completo la maggior parte dell'heap viene percorsa e copiata, quindi se gran parte dell'heap era in scambio, il GC deve ricaricare tutto nella memoria principale.
jrudolph,

1
Ottima risposta kdgregory! Sto correndo in un ambiente incorporato usando un CF che NON ha spazio di scambio. Quindi, in base alla tua risposta, tutti i miei valori VIRT, SWAP e nFLT provengono da file mappati in memoria ... il che ora ha senso per mew. Sai se il valore SWAP rappresenta le pagine che non sono state ancora caricate in memoria o le pagine che sono state scambiate in memoria, o entrambe? Come possiamo avere un'idea del possibile thrashing (mappa continua in poi swap out)?
Jeach,

2
@Jeach - Sono rimasto sorpreso dal fatto che sia stato segnalato qualsiasi scambio, quindi ho avviato il mio "Linux viaggiante" (una chiavetta USB con Ubuntu 10.04 e nessuno scambio). Quando ho abilitato la colonna "SWAP" in alto , ho visto che Eclipse aveva 509m. Quando poi l'ho guardato con pmap , lo spazio virtuale totale era di 650m. Quindi sospetto che la figura "SWAP" rappresenti tutte le pagine su disco, non solo quelle che non sono in memoria.
kdgregory,

2
Per quanto riguarda la tua seconda domanda: se stai leggendo costantemente le pagine dalla scheda flash, il tuo tempo di attesa IO (mostrato nel sommario in alto come "% wa") dovrebbe essere alto. Fai attenzione, tuttavia, che questo sarà elevato per qualsiasi attività, in particolare le scritture (supponendo che il tuo programma faccia qualsiasi).
kdgregory,

1
> Il blocco 1M è uno stack di thread; Non so cosa vada nel blocco 4K. Il blocco 4K - che è contrassegnato come privo di autorizzazioni di lettura o scrittura - è probabilmente un blocco di protezione. In caso di overflow dello stack, si accede a quest'area, che attiva un errore, che la JVM può quindi gestire generando Java StackOverflowException. Questo è molto più economico rispetto al controllo del puntatore dello stack ad ogni chiamata del metodo. Le aree di guardia senza set di autorizzazioni possono anche essere utilizzate in altri contesti.
eriksoe,

38

Esiste un problema noto con Java e glibc> = 2.10 (include Ubuntu> = 10.04, RHEL> = 6).

La cura è di impostare questo env. variabile:

export MALLOC_ARENA_MAX=4

Se stai eseguendo Tomcat, puoi aggiungerlo al TOMCAT_HOME/bin/setenv.shfile.

Per Docker, aggiungilo a Dockerfile

ENV MALLOC_ARENA_MAX=4

C'è un articolo IBM sull'impostazione di MALLOC_ARENA_MAX https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en

Questo post sul blog dice

è noto che la memoria residente si insinua in modo simile a una perdita di memoria o frammentazione della memoria.

Esiste anche un bug JDK aperto JDK -8193521 "glibc spreca memoria con la configurazione predefinita"

cerca MALLOC_ARENA_MAX su Google o SO per ulteriori riferimenti.

Potresti voler ottimizzare anche altre opzioni di malloc per ottimizzare la frammentazione ridotta della memoria allocata:

# tune glibc memory allocation, optimize for low fragmentation
# limit the number of arenas
export MALLOC_ARENA_MAX=2
# disable dynamic mmap threshold, see M_MMAP_THRESHOLD in "man mallopt"
export MALLOC_MMAP_THRESHOLD_=131072
export MALLOC_TRIM_THRESHOLD_=131072
export MALLOC_TOP_PAD_=131072
export MALLOC_MMAP_MAX_=65536

Questa risposta mi ha davvero aiutato su un Ubuntu Server a 64 bit con un server TomEE che ha ottenuto un litte a "consumare mem". Il collegamento all'articolo IBM è davvero una spiegazione profonda. Grazie ancora per questo buon suggerimento!
MWiesner,

1
La JVM potrebbe perdere la memoria nativa che porta a sintomi simili. Vedi stackoverflow.com/a/35610063/166062 . Anche le istanze GZIPInputStream e GZIPOutputStream non chiuse potrebbero essere la fonte della perdita.
Lari Hotari,

3
Esiste un bug JVM in Java 8, che provoca una crescita della memoria nativa illimitata: bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8164293 - Se questo ti sta influenzando, l'utilizzo MALLOC_ARENA_MAXpotrebbe rallentare la crescita della memoria, ma non risolvere completamente il problema.
Outofcoffee

@LariHotari apprezza molto i tuoi sforzi per sottolineare la versione di glibc e redhat
Sam,

2
Java 8u131 contiene bug fix backported per il relativo bug JVM JDK-8164293 bugs.openjdk.java.net/browse/JDK-8178124 .
Lari Hotari,

9

La quantità di memoria allocata per il processo Java è praticamente alla pari con quello che mi aspetterei. Ho avuto problemi simili con Java su sistemi embedded / con memoria limitata. L'esecuzione di qualsiasi applicazione con limiti di VM arbitrari o su sistemi che non dispongono di quantità adeguate di scambio tende a rompersi. Sembra essere la natura di molte app moderne che non sono progettate per l'uso su sistemi a risorse limitate.

Hai alcune altre opzioni che puoi provare a limitare il footprint di memoria della tua JVM. Ciò potrebbe ridurre il footprint della memoria virtuale:

-XX: ReservedCodeCacheSize = 32m Dimensione cache del codice riservato (in byte) - dimensione massima della cache del codice. [Solaris 64-bit, amd64 e -server x86: 48m; in 1.5.0_06 e precedenti, Solaris 64-bit e and64: 1024m.]

-XX: MaxPermSize = 64m Dimensione della generazione permanente. [5.0 e versioni successive: le macchine virtuali a 64 bit sono ridimensionate del 30% in più; 1.4 amd64: 96m; 1.3.1 -client: 32m.]

Inoltre, dovresti anche impostare -Xmx (dimensione heap massima) su un valore il più vicino possibile all'utilizzo effettivo della memoria di picco della tua applicazione. Credo che il comportamento predefinito di JVM sia ancora quello di raddoppiare la dimensione dell'heap ogni volta che lo espande al massimo. Se inizi con un heap di 32 M e la tua app raggiunge il picco di 65 M, l'heap finirebbe per crescere 32 M -> 64 M -> 128 M.

Potresti anche provare questo per rendere la VM meno aggressiva nella crescita dell'heap:

-XX: MinHeapFreeRatio = 40 Percentuale minima di heap libero dopo GC per evitare l'espansione.

Inoltre, da ciò che ricordo sperimentando alcuni anni fa, il numero di librerie native caricate ha avuto un impatto enorme sul footprint minimo. Caricamento java.net.Socket aggiunto più di 15 M se ricordo bene (e probabilmente non lo faccio).


7

Sun JVM richiede molta memoria per HotSpot e mappa nelle librerie di runtime nella memoria condivisa.

Se la memoria è un problema, utilizzare un'altra JVM adatta per l'incorporamento. IBM ha j9 e c'è il "jamvm" Open Source che usa le librerie del percorso di classe GNU. Inoltre Sun ha Squeak JVM in esecuzione su SunSPOTS, quindi ci sono alternative.


È un'opzione per disabilitare l'hot spot?
Mario Ortegón,

Forse. Controlla le opzioni della riga di comando per la JVM che usi.
Thorbjørn Ravn Andersen,

3

Solo un pensiero, ma si può verificare l'influenza di un ulimit -vopzione .

Questa non è una soluzione effettiva poiché limiterebbe lo spazio degli indirizzi disponibile per tutto il processo, ma ciò consentirebbe di verificare il comportamento dell'applicazione con una memoria virtuale limitata.


Questo è esattamente il mio problema. My Heap è impostato su 64M, ma Linux riserva 204 MB. Se ho impostato l'ulimit al di sotto di 204, l'applicazione non funzionerà affatto.
Mario Ortegón,

Interessante: l'impostazione di ulimit potrebbe avere effetti collaterali non intenzionali per altri processi, spiegando perché l'applicazione non è in grado di funzionare.
VonC,

Il problema sembra essere che Java richiede di riservare questa maggiore quantità di memoria virtuale anche se non la utilizzerà. In Windows la memoria virtuale utilizzata e l'impostazione Xmx sono piuttosto vicine.
Mario Ortegón,

Hai provato con un JRockit JVM?
VonC,

Poiché l'allocazione di memoria della JVM è la somma dell'allocazione dell'heap e della dimensione del permesso (la prima può essere riparata usando le opzioni -Xms e -Xmx), hai provato alcune impostazioni con -XX: PermSize e -XX: MaxPermSize (impostazione predefinita da 32 MB a 64 MB a seconda della versione JVM)?
VonC,

3

Un modo per ridurre il livello di heap di un sistema con risorse limitate potrebbe essere quello di giocare con la variabile -XX: MaxHeapFreeRatio. Di solito è impostato su 70 ed è la percentuale massima dell'heap che è libera prima che il GC lo riduca. Impostandolo su un valore inferiore, nel profiler jvisualvm vedrai, ad esempio, che per il tuo programma viene generalmente utilizzata una heap più piccola.

EDIT: per impostare piccoli valori per -XX: MaxHeapFreeRatio devi anche impostare -XX: MinHeapFreeRatio ad es.

java -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=25 HelloWorld

EDIT2: aggiunto un esempio per un'applicazione reale che avvia e svolge la stessa attività, una con parametri predefiniti e una con 10 e 25 come parametri. Non ho notato alcuna reale differenza di velocità, anche se in teoria java dovrebbe impiegare più tempo per aumentare l'heap in quest'ultimo esempio.

Parametri di default

Alla fine, l'heap massimo è 905, l'heap utilizzato è 378

MinHeap 10, MaxHeap 25

Alla fine, l'heap massimo è 722, l'heap utilizzato è 378

Questo in realtà ha un certo impatto, poiché la nostra applicazione viene eseguita su un server desktop remoto e molti utenti possono eseguirla contemporaneamente.


1

Sun Java java 1.4 ha i seguenti argomenti per controllare la dimensione della memoria:

-Xmsn Specifica la dimensione iniziale, in byte, del pool di allocazione della memoria. Questo valore deve essere un multiplo di 1024 maggiore di 1 MB. Aggiungi la lettera k o K per indicare i kilobyte, oppure m o M per indicare i megabyte. Il valore predefinito è 2 MB. Esempi:

           -Xms6291456
           -Xms6144k
           -Xms6m

-Xmxn Specifica la dimensione massima, in byte, del pool di allocazione della memoria. Questo valore deve avere un multiplo di 1024 maggiore di 2 MB. Aggiungi la lettera k o K per indicare i kilobyte, oppure m o M per indicare i megabyte. Il valore predefinito è 64 MB. Esempi:

           -Xmx83886080
           -Xmx81920k
           -Xmx80m

http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/java.html

Java 5 e 6 ne hanno un po 'di più. Vedi http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp


1
Il problema che ho non è con la dimensione dell'heap, ma con la quantità di memoria virtuale assegnata da Linux
Mario Ortegón,

Leggi la spiegazione di kdgregory. Riducendo la dimensione dell'heap, "Nuova dimensione" e gli altri parametri configurabili si ridurrà la quantità di memoria REAL che jvm accetta.
Paul Tomblin,

Potrebbe avere un problema legittimo. Alcune applicazioni (come quella che ho scritto) eseguono il mmap di un file da 1 GB e alcuni sistemi hanno solo 2 GB di memoria virtuale, alcuni dei quali vengono riempiti con librerie condivise. E se questo è il problema, dovrebbe assolutamente disabilitare la randomizzazione DSO. C'è un'opzione in / proc.
Zan Lynx,

0

No, non è possibile configurare la quantità di memoria richiesta dalla VM. Tuttavia, si noti che questa è memoria virtuale, non residente, quindi rimane lì senza danni se non effettivamente utilizzata.

In alternativa, puoi provare qualche altra JVM poi Sun, con un ingombro di memoria ridotto, ma non posso consigliarti qui.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.