Domande di intervista sulla gestione della memoria in Java (+ risposte)

Questo articolo fa parte di una serie: • Domande di intervista sulle raccolte Java

• Domande di intervista sul sistema di tipo Java

• Domande di intervista sulla concorrenza Java (+ risposte)

• Struttura delle classi Java e domande di colloquio sull'inizializzazione

• Domande di intervista Java 8 (+ risposte)

• Gestione della memoria in Java Interview Questions (+ Risposte) (articolo corrente) • Java Generics Interview Questions (+ Risposte)

• Domande di intervista sul controllo del flusso Java (+ risposte)

• Domande di intervista sulle eccezioni Java (+ risposte)

• Domande di intervista sulle annotazioni Java (+ risposte)

• Domande principali per l'intervista su Spring Framework

1. Introduzione

In questo articolo, esploreremo alcune domande sulla gestione della memoria che appaiono frequentemente durante le interviste agli sviluppatori Java. La gestione della memoria è un'area con cui non molti sviluppatori hanno familiarità.

In effetti, gli sviluppatori generalmente non devono affrontare direttamente questo concetto, poiché la JVM si prende cura dei dettagli essenziali. A meno che qualcosa non vada seriamente storto, anche gli sviluppatori esperti potrebbero non avere informazioni accurate sulla gestione della memoria a portata di mano.

D'altra parte, questi concetti sono in realtà abbastanza prevalenti nelle interviste, quindi entriamo subito.

2. Domande

Q1. Cosa significa l'istruzione "Memory Is Managed in Java"?

La memoria è la risorsa chiave richiesta da un'applicazione per funzionare in modo efficace e, come qualsiasi risorsa, è scarsa. Pertanto, la sua assegnazione e deallocazione da e verso le applicazioni o parti diverse di un'applicazione richiedono molta cura e considerazione.

Tuttavia, in Java, uno sviluppatore non ha bisogno di allocare e deallocare esplicitamente la memoria - la JVM e più specificamente il Garbage Collector - ha il compito di gestire l'allocazione della memoria in modo che lo sviluppatore non debba farlo.

Ciò è contrario a quanto accade in linguaggi come il C, dove un programmatore ha accesso diretto alla memoria e fa letteralmente riferimento alle celle di memoria nel suo codice, creando molto spazio per le perdite di memoria.

Q2. Che cos'è la raccolta dei rifiuti e quali sono i suoi vantaggi?

La garbage collection è il processo di analisi della memoria heap, identificazione degli oggetti in uso e quali no e dell'eliminazione degli oggetti inutilizzati.

Un oggetto in uso o un oggetto referenziato significa che una parte del programma mantiene ancora un puntatore a quell'oggetto. Un oggetto inutilizzato, o un oggetto non referenziato, non è più referenziato da nessuna parte del programma. Quindi la memoria utilizzata da un oggetto non referenziato può essere recuperata.

Il più grande vantaggio della garbage collection è che ci rimuove il peso dell'allocazione / deallocazione manuale della memoria in modo che possiamo concentrarci sulla risoluzione del problema in questione.

Q3. Ci sono svantaggi della raccolta dei rifiuti?

Sì. Ogni volta che viene eseguito il Garbage Collector, ha un effetto sulle prestazioni dell'applicazione. Questo perché tutti gli altri thread dell'applicazione devono essere interrotti per consentire al thread del Garbage Collector di svolgere efficacemente il proprio lavoro.

A seconda dei requisiti dell'applicazione, questo può essere un problema reale inaccettabile dal cliente. Tuttavia, questo problema può essere notevolmente ridotto o addirittura eliminato attraverso un'abile ottimizzazione e messa a punto del garbage collector e utilizzando diversi algoritmi GC.

Q4. Qual è il significato del termine "Stop-The-World"?

Quando il thread del Garbage Collector è in esecuzione, gli altri thread vengono arrestati, il che significa che l'applicazione viene interrotta momentaneamente. Questo è analogo alla pulizia della casa o alla fumigazione in cui agli occupanti viene negato l'accesso fino al completamento del processo.

A seconda delle esigenze di un'applicazione, la garbage collection "ferma il mondo" può causare un blocco inaccettabile. Questo è il motivo per cui è importante eseguire l'ottimizzazione del Garbage Collector e l'ottimizzazione della JVM in modo che il blocco riscontrato sia almeno accettabile.

Q5. Cosa sono stack e heap? Cosa è memorizzato in ciascuna di queste strutture di memoria e come sono correlate?

Lo stack è una parte della memoria che contiene informazioni sulle chiamate di metodi annidate fino alla posizione corrente nel programma. Contiene inoltre tutte le variabili locali ei riferimenti agli oggetti sull'heap definiti nei metodi attualmente in esecuzione.

Questa struttura consente al runtime di tornare dal metodo conoscendo l'indirizzo da cui è stato chiamato e anche di cancellare tutte le variabili locali dopo essere usciti dal metodo. Ogni thread ha il proprio stack.

L'heap è una grande quantità di memoria destinata all'allocazione di oggetti. Quando crei un oggetto con la nuova parola chiave, viene allocato nell'heap. Tuttavia, il riferimento a questo oggetto risiede nello stack.

Q6. Cos'è la raccolta generazionale dei rifiuti e cosa la rende un approccio popolare alla raccolta dei rifiuti?

La garbage collection generazionale può essere definita liberamente come la strategia utilizzata dal garbage collector in cui l'heap è suddiviso in un numero di sezioni chiamate generazioni, ciascuna delle quali conterrà gli oggetti in base alla loro "età" nell'heap.

Ogni volta che il Garbage Collector è in esecuzione, il primo passaggio del processo è chiamato marcatura. Qui è dove il garbage collector identifica quali pezzi di memoria sono in uso e quali no. Questo può essere un processo che richiede molto tempo se tutti gli oggetti in un sistema devono essere scansionati.

Man mano che vengono allocati sempre più oggetti, l'elenco di oggetti cresce e cresce portando a tempi di raccolta dei rifiuti sempre più lunghi. Tuttavia, l'analisi empirica delle applicazioni ha dimostrato che la maggior parte degli oggetti ha vita breve.

Con la garbage collection generazionale, gli oggetti vengono raggruppati in base alla loro "età" in termini di quanti cicli di garbage collection sono sopravvissuti. In questo modo, la maggior parte del lavoro si è distribuita su vari cicli di raccolta minori e maggiori.

Oggi quasi tutti i netturbini sono generazionali. Questa strategia è così popolare perché, nel tempo, si è dimostrata la soluzione ottimale.

Q7. Descrivi in ​​dettaglio come funziona la raccolta generazionale dei rifiuti

Per comprendere correttamente come funziona la garbage collection generazionale, è importante prima ricordare come è strutturato l'heap Java per facilitare la garbage collection generazionale.

Il mucchio è suddiviso in spazi o generazioni più piccoli. Questi spazi sono Young Generation, Old or Tenured Generation e Permanent Generation.

La giovane generazione ospita la maggior parte degli oggetti di nuova creazione . Uno studio empirico della maggior parte delle applicazioni mostra che la maggior parte degli oggetti ha una vita breve e quindi diventa presto idonea per la raccolta. Pertanto, nuovi oggetti iniziano il loro viaggio qui e vengono "promossi" nello spazio della vecchia generazione solo dopo aver raggiunto una certa "età".

Il termine "età" nella Garbage Collection generazionale si riferisce al numero di cicli di raccolta sopravvissuti all'oggetto .

Lo spazio della giovane generazione è ulteriormente suddiviso in tre spazi: uno spazio Eden e due spazi sopravvissuti come Survivor 1 (s1) e Survivor 2 (s2).

La vecchia generazione ospita oggetti che hanno vissuto nella memoria più a lungo di una certa “età” . In questo spazio vengono promossi gli oggetti sopravvissuti alla raccolta dei rifiuti della giovane generazione. È generalmente più grande delle giovani generazioni. Poiché è di dimensioni maggiori, la raccolta dei rifiuti è più costosa e si verifica meno frequentemente rispetto alle giovani generazioni.

La generazione permanente o più comunemente chiamata PermGen, contiene i metadati richiesti dalla JVM per descrivere le classi e i metodi utilizzati nell'applicazione. Contiene anche il pool di stringhe per l'archiviazione di stringhe interne. Viene popolato dalla JVM in fase di esecuzione in base alle classi in uso dall'applicazione. Inoltre, qui possono essere memorizzati classi e metodi della libreria della piattaforma.

Innanzitutto, tutti i nuovi oggetti vengono assegnati allo spazio Eden . Entrambi gli spazi sopravvissuti iniziano vuoti. Quando lo spazio Eden si riempie, viene attivata una piccola raccolta di rifiuti. Gli oggetti referenziati vengono spostati nel primo spazio superstite. Gli oggetti senza riferimenti vengono eliminati.

Durante il successivo GC minore, la stessa cosa accade allo spazio Eden. Gli oggetti non referenziati vengono eliminati e gli oggetti referenziati vengono spostati in uno spazio superstite. Tuttavia, in questo caso, vengono spostati nel secondo spazio superstite (S2).

Inoltre, gli oggetti dell'ultimo GC minore nel primo spazio sopravvissuto (S1) hanno la loro età incrementata e vengono spostati in S2. Una volta che tutti gli oggetti sopravvissuti sono stati spostati in S2, sia lo spazio S1 che l'Eden vengono liberati. A questo punto, S2 contiene oggetti con età diverse.

Al successivo GC minore, lo stesso processo viene ripetuto. Tuttavia questa volta gli spazi sopravvissuti cambiano. Gli oggetti referenziati vengono spostati in S1 sia da Eden che da S2. Gli oggetti sopravvissuti invecchiano. Eden e S2 vengono cancellati.

Dopo ogni ciclo minore di Garbage Collection, viene verificata l'età di ogni oggetto. Quelli che hanno raggiunto una certa età arbitraria, ad esempio 8 anni, vengono promossi dalla generazione giovane alla generazione anziana o di ruolo. Per tutti i successivi cicli GC minori, gli oggetti continueranno a essere promossi nello spazio di vecchia generazione.

Questo praticamente esaurisce il processo di raccolta dei rifiuti nelle giovani generazioni. Alla fine, verrà eseguita un'importante raccolta di rifiuti sulla vecchia generazione che pulisce e compatta quello spazio. Per ogni GC principale, ci sono diversi GC minori.

Q8. Quando un oggetto diventa idoneo per la raccolta dei rifiuti? Descrivi in ​​che modo il Gc raccoglie un oggetto idoneo?

Un oggetto diventa idoneo per Garbage Collection o GC se non è raggiungibile da alcun thread attivo o da riferimenti statici.

Il caso più semplice in cui un oggetto diventa idoneo per la garbage collection è se tutti i suoi riferimenti sono nulli. Anche le dipendenze cicliche senza alcun riferimento esterno attivo sono idonee per GC. Quindi, se l'oggetto A fa riferimento all'oggetto B e l'oggetto B fa riferimento all'oggetto A e non hanno altri riferimenti live, entrambi gli oggetti A e B saranno idonei per la Garbage Collection.

Un altro caso ovvio è quando un oggetto genitore è impostato su null. Quando un oggetto cucina fa riferimento internamente a un oggetto frigorifero e a un oggetto lavello e l'oggetto cucina è impostato su null, sia il frigorifero che il lavello diventeranno idonei per la raccolta dei rifiuti insieme al loro genitore, la cucina.

Q9. Come si attiva la Garbage Collection dal codice Java?

Tu, come programmatore Java, non puoi forzare la garbage collection in Java ; si attiverà solo se JVM pensa di aver bisogno di una garbage collection basata sulla dimensione dell'heap Java.

Prima di rimuovere un oggetto dalla memoria, il thread di garbage collection richiama il metodo finalize () di quell'oggetto e offre l'opportunità di eseguire qualsiasi tipo di pulizia richiesta. È anche possibile richiamare questo metodo di un codice oggetto, tuttavia, non vi è alcuna garanzia che si verifichi la garbage collection quando si chiama questo metodo.

Inoltre, ci sono metodi come System.gc () e Runtime.gc () che viene utilizzato per inviare la richiesta di Garbage Collection a JVM ma non è garantito che venga eseguita la Garbage Collection.

Q10. Cosa succede quando non c'è abbastanza spazio per immagazzinare nuovi oggetti?

Se non è presente spazio di memoria per creare un nuovo oggetto in Heap, Java Virtual Machine genera OutOfMemoryError o più specificamente java.lang.OutOfMemoryError spazio heap.

Q11. È possibile "resuscitare" un oggetto che è diventato idoneo per la raccolta dei rifiuti?

Quando un oggetto diventa idoneo per la garbage collection, il GC deve eseguire il metodo finalize su di esso. Il metodo finalize è garantito per essere eseguito solo una volta, quindi il GC contrassegna l'oggetto come finalizzato e gli dà una pausa fino al ciclo successivo.

Nel metodo finalize puoi tecnicamente "resuscitare" un oggetto, ad esempio assegnandolo a un campo statico . L'oggetto diventerebbe di nuovo attivo e non idoneo per la garbage collection, quindi il GC non lo raccoglierebbe durante il ciclo successivo.

L'oggetto, tuttavia, verrebbe contrassegnato come finalizzato, quindi quando sarebbe nuovamente idoneo, il metodo finalize non verrà chiamato. In sostanza, puoi trasformare questo trucco della "resurrezione" solo una volta per tutta la vita dell'oggetto. Fai attenzione che questo brutto trucco dovrebbe essere usato solo se sai davvero cosa stai facendo, tuttavia, la comprensione di questo trucco fornisce alcune informazioni su come funziona il GC.

Q12. Descrivi riferimenti forti, deboli, deboli e fantasma e il loro ruolo nella raccolta dei rifiuti.

Per quanto la memoria sia gestita in Java, un ingegnere potrebbe dover eseguire la massima ottimizzazione possibile per ridurre al minimo la latenza e massimizzare il throughput, nelle applicazioni critiche. Per quanto sia impossibile controllare esplicitamente quando viene attivata la garbage collection nella JVM, è possibile influenzare il modo in cui si verifica per quanto riguarda gli oggetti che abbiamo creato.

Java ci fornisce oggetti di riferimento per controllare la relazione tra gli oggetti che creiamo e il garbage collector.

Per impostazione predefinita, ogni oggetto che creiamo in un programma Java è fortemente referenziato da una variabile:

StringBuilder sb = new StringBuilder();

Nello snippet precedente, la nuova parola chiave crea un nuovo oggetto StringBuilder e lo memorizza nell'heap. La variabile sb quindi memorizza un forte riferimento a questo oggetto. Ciò che questo significa per il garbage collector è che il particolare oggetto StringBuilder non è idoneo per la raccolta a causa di un forte riferimento ad esso tenuto da sb . La storia cambia solo quando annulliamo Sai Baba in questo modo:

sb = null;

Dopo aver chiamato la riga precedente, l'oggetto sarà quindi idoneo per la raccolta.

Possiamo cambiare questa relazione tra l'oggetto e il garbage collector avvolgendolo esplicitamente all'interno di un altro oggetto di riferimento che si trova all'interno del pacchetto java.lang.ref .

È possibile creare un riferimento morbido all'oggetto sopra in questo modo:

StringBuilder sb = new StringBuilder(); SoftReference sbRef = new SoftReference(sb); sb = null;

Nello snippet sopra, abbiamo creato due riferimenti all'oggetto StringBuilder . La prima riga crea un riferimento forte sb e la seconda riga crea un riferimento morbido sbRef . La terza riga dovrebbe rendere l'oggetto idoneo per la raccolta, ma il garbage collector rimanderà la raccolta a causa di sbRef .

La storia cambierà solo quando la memoria diventerà stretta e la JVM sarà sul punto di lanciare un errore OutOfMemory . In altre parole, gli oggetti con solo riferimenti temporanei vengono raccolti come ultima risorsa per recuperare la memoria.

Un riferimento debole può essere creato in modo simile utilizzando la classe WeakReference . Quando sb è impostato su null e l' oggetto StringBuilder ha solo un riferimento debole, il garbage collector della JVM non avrà assolutamente alcun compromesso e raccoglierà immediatamente l'oggetto al ciclo successivo.

Un riferimento fantasma è simile a un riferimento debole e un oggetto con solo riferimenti fantasma verrà raccolto senza attendere. Tuttavia, i riferimenti fantasma vengono accodati non appena i loro oggetti vengono raccolti. Possiamo interrogare la coda di riferimento per sapere esattamente quando l'oggetto è stato raccolto.

Q13. Supponiamo di avere un riferimento circolare (due oggetti che fanno riferimento a vicenda). Una tale coppia di oggetti potrebbe diventare idonea per la raccolta dei rifiuti e perché?

Sì, una coppia di oggetti con un riferimento circolare può diventare idonea per la garbage collection. Ciò è dovuto al modo in cui il garbage collector di Java gestisce i riferimenti circolari. Considera gli oggetti live non quando hanno alcun riferimento ad essi, ma quando sono raggiungibili navigando nell'oggetto grafico a partire da qualche radice di garbage collection (una variabile locale di un live thread o un campo statico). Se una coppia di oggetti con un riferimento circolare non è raggiungibile da nessuna radice, è considerata idonea per la garbage collection.

Q14. Come vengono rappresentate le stringhe in memoria?

Una stringa di esempio in Java è un oggetto con due campi: un valore char [] campo ed un int hash campo. Il campo valore è un array di caratteri che rappresenta la stringa stessa, e il campo hash contiene l' hashCode di una stringa che viene inizializzata con zero, calcolata durante la prima chiamata hashCode () e da allora messa in cache. Come curioso caso limite, se un hashCode di una stringa ha un valore zero, deve essere ricalcolato ogni volta che viene chiamato hashCode () .

La cosa importante è che un'istanza String è immutabile: non è possibile ottenere o modificare l' array char [] sottostante . Un'altra caratteristica delle stringhe è che le stringhe costanti statiche vengono caricate e memorizzate nella cache in un pool di stringhe. Se hai più oggetti String identici nel codice sorgente, sono tutti rappresentati da una singola istanza in fase di runtime.

Q15. Cos'è uno Stringbuilder e quali sono i suoi casi d'uso? Qual è la differenza tra l'aggiunta di una stringa a un generatore di stringhe e la concatenazione di due stringhe con un operatore +? In che modo Stringbuilder differisce da Stringbuffer?

StringBuilder consente di manipolare sequenze di caratteri aggiungendo, eliminando e inserendo caratteri e stringhe. Questa è una struttura dati mutevole, al contrario della classe String che è immutabile.

Quando si concatenano due istanze String , viene creato un nuovo oggetto e le stringhe vengono copiate. Ciò potrebbe comportare un enorme sovraccarico del Garbage Collector se è necessario creare o modificare una stringa in un ciclo. StringBuilder consente di gestire le manipolazioni delle stringhe in modo molto più efficiente.

StringBuffer è diverso da StringBuilder in quanto è thread-safe. Se è necessario manipolare una stringa in un singolo thread, utilizzare invece StringBuilder .

3. Conclusione

In questo articolo, abbiamo coperto alcune delle domande più comuni che appaiono di frequente nelle interviste agli ingegneri Java. Le domande sulla gestione della memoria vengono poste principalmente ai candidati Senior Java Developer poiché l'intervistatore si aspetta che abbiate costruito applicazioni non banali che sono, molte volte, afflitte da problemi di memoria.

Questo non dovrebbe essere trattato come un elenco esaustivo di domande, ma piuttosto un trampolino di lancio per ulteriori ricerche. Noi di Baeldung vi auguriamo successo in tutte le prossime interviste.

Successivo » Domande di intervista Java Generics (+ Risposte) « Precedente Domande di intervista Java 8 (+ Risposte)