Anatomia della memoria

Esistono due tipi di memoria:

  • heap
  • stack

Gli oggetti vengono gestiti dinamicamente e la gestione dinamica consiste nell’allocare gli oggetti nella heap.

Il compilatore mette nello stack delle strutture dati dove le variabile locali dei metodi saranno scritte nei frame.

Quindi la gestione dello stack viene prestabilita compilando il codice, la gestione della heap è una questione dinamica.

La parola chiave static

Un campo static esiste in una sola locazione di memoria, allocata prima di qualsiasi oggetto della classe in una zione speciale di memoria nativa chiamata MetaSpace.

E’ condiviso da tutte le sue istanze e quindi è relativo all’intera classe.

Per ogni campo non statico esiste una locazione di memoria per ogni oggetto, allocata a seguito dell’istruzione new

Esempio classe Tornello

public class Tornello
{
	static private int passaggi;
 
	public void passa() {passaggi++;}
 
	public static int getPassaggi() {return passaggi;}
 
	public static void main (String[] args)
	{
		Tornello t1 = new Tornello();
		t1.passa();
		Tornello t2 = new Tornello();
		for (int k = 0; k < 10; k++) t2.passa();
		int g; 
		String s = null;
	}
}

Heap, stack e metaspace

  • Nello stack si rappresentano i frame di attivazione (chiamate ai metodi) e le variabili locali
  • Nella heap si rappresenta l’allocazione degli oggetti
  • Nel metaspace si rappresenta l’allocazione dei campi

Drawing 2024-08-09 17.53.18.excalidraw

Quando compilo il codice la JVM usa un modulo chiamato class loader che controlla innanzitutto la presenza del metodo main.

Quindi controlla se ci sono campi statici. Nell’esempio tornello c’è un campo statico (static private int passaggi;).

Quindi passaggi verrĂ  allocato nel metaspace

Dopo aver controllato tutti i campi statici, la JVM passa al metodo main.

Il main è un metodo che ha sempre un vettore di stringhe.

Se ci sono degli argomenti, viene costruito un vettore i cui elementi sono le stringhe che sono passate come argomento di programma.

Se non ci sono stringhe, il vettore sarĂ  vuoto.

La JVM crea il frame di attivazione del metodo main.

Ogni volta che viene chiamato un metodo, tutte le variabili locali che corrispondono ai parametri attuali, vengono copiati.

Tutto ciò che viene passato come metodo viene copiato e allocato nello stack, quindi args diventa una variabile

Ora siamo nella riga

Tornello t1 = new Tornello();

Viene chiamato il costruttore di default e viene allocato nello stack

E’ una dichiarazione di t1 di tipo Tornello.

Costruisce l’oggetto e lo alloca nella heap.

Ora ho la chiamata del metodo passa() a partire dal riferimento t1.

t1.passa();

Avremo quindi un nuovo frame che si posizionerĂ  sopra il main.

La JVM crea un’altra area di memoria per rappresentare ciò che deve fare nell’eseguire il metodo passa().

Nel passaggio successivo t1.passa() viene distrutto e al suo posso viene scritto il frame Tornello()

Tornello t2 = new t2 = new Tornello();

Stiamo chiamando un metodo, quello di costruzione del tornello, il costruttore di default.

Quando chiamo il costruttore la JVM inizia ad allocare il progetto, controlla se ci sono campi e istruzioni e alloca il progetto nella heap.

Tornello t2 = new Tornello();

Viene allocato t2 che è una variabile locale.

for (int k = 0; k < 10; k++) t2.passa();

Con quest’istruzione intendiamo chiamare passa a partire dall’oggetto t2.

k = 0 diventa temporaneamente una variabile locale di tipo intero.

Viene allocata nel frame main

Viene richiamato il metodo passa().

Il frame t2.passa() viene distrutto

K diventa 1 e richiamo di nuovo passa()

Continuo a chiamare passa() finché k>=10

K sparisce dal frame alla fine del ciclo for

int g;

viene creata una variabile primitiva g che avrĂ  come valore undef.

String s = null;

Viene creata una variabile locale s di tipo String che ha come valore null.

+