Memoria stack e heap in Java


99

A quanto ho capito, in Java, la memoria dello stack contiene primitive e invocazioni di metodi e la memoria heap viene utilizzata per archiviare oggetti.

Supponiamo che io abbia una lezione

class A {
       int a ;
       String b;
       //getters and setters
}
  1. Dove verrà archiviata la primitiva ain classe A?

  2. Perché esiste la memoria heap? Perché non possiamo archiviare tutto in pila?

  3. Quando l'oggetto viene raccolto spazzatura, lo stack associato all'oggetto viene distrutto?



@ S. Lott, tranne per il fatto che questo riguarda Java, non C.
Péter Török,

@ Péter Török: concordato. Mentre l'esempio di codice è Java, non esiste alcun tag per indicare che è solo Java. E il principio generale dovrebbe applicarsi sia a Java che a C. Inoltre, ci sono molte risposte a questa domanda su StackTranslate.it.
S.Lott

9
@SteveHaigh: su questo sito, tutti sono troppo preoccupati per sapere se qualcosa appartiene qui ... Mi chiedo cosa stia condividendo questo sito con tutta la pignoleria sul fatto che le domande appartengano o meno.
Sam Goldberg,

Risposte:


106

La differenza di base tra stack e heap è il ciclo di vita dei valori.

I valori dello stack esistono solo nell'ambito della funzione in cui vengono creati. Una volta restituiti, vengono eliminati.
I valori di heap tuttavia esistono sull'heap. Vengono creati ad un certo punto nel tempo e distrutti in un altro (o da GC o manualmente, a seconda della lingua / runtime).

Ora Java memorizza solo le primitive nello stack. Ciò mantiene lo stack piccolo e aiuta a mantenere piccoli frame dello stack individuale, consentendo così più chiamate nidificate.
Gli oggetti vengono creati nell'heap e solo i riferimenti (che a loro volta sono primitivi) vengono passati in giro nello stack.

Pertanto, se si crea un oggetto, questo viene inserito nell'heap, con tutte le variabili che gli appartengono, in modo che possa persistere dopo il ritorno della chiamata di funzione.


2
"e solo riferimenti (che a loro volta sono primitivi)" Perché dire che i riferimenti sono primitivi? Puoi chiarire per favore?
Geek,

5
@Geek: Perché si applica la definizione comune di tipi di dati primitivi: "un tipo di dati fornito da un linguaggio di programmazione come blocco base" . Potresti anche notare che i riferimenti sono elencati tra gli esempi canonici più in basso nell'articolo .
back2dos

4
@Geek: in termini di dati, puoi visualizzare uno qualsiasi dei tipi di dati primitivi, inclusi i riferimenti, come numeri. Anche chars sono numeri e possono essere usati in modo intercambiabile come così. I riferimenti sono anche solo numeri che si riferiscono a un indirizzo di memoria, lunghi 32 o 64 bit (anche se non possono essere usati come tali - a meno che non si scherzi sun.misc.Unsafe).
Sune Rasmussen,

3
La terminologia di questa risposta è errata. Secondo la specifica del linguaggio Java, i riferimenti NON sono primitivi. L'essenza di ciò che dice la Risposta è però corretta. (Mentre è possibile fare un ragionamento che i riferimenti sono "in un certo senso" primitivo è dal di The JLS definisce la terminologia per Java, e si dice che i tipi primitivi sono. boolean, byte, short, char, int, long, floatE double.)
Stephen C

4
Come ho trovato in questo articolo , Java può archiviare oggetti nello stack (o persino nei registri per piccoli oggetti di breve durata). La JVM può fare un bel po 'sotto le coperte. Non è esattamente corretto dire "Ora Java memorizza solo le primitive nello stack."

49

Dove sono memorizzati i campi primitivi?

I campi primitivi vengono archiviati come parte dell'oggetto che viene istanziato da qualche parte . Il modo più semplice di pensare a dove si trova è l'heap. Tuttavia , questo non è sempre il caso. Come descritto nella teoria e nella pratica di Java: Leggende delle prestazioni urbane, rivisitate :

Le JVM possono utilizzare una tecnica chiamata analisi di escape, in base alla quale possono dire che determinati oggetti rimangono confinati in un singolo thread per tutta la loro durata e che la durata è limitata dalla durata di un determinato stack frame. Tali oggetti possono essere allocati in modo sicuro nello stack anziché nell'heap. Ancora meglio, per piccoli oggetti, la JVM può ottimizzare completamente l'allocazione e semplicemente issare i campi dell'oggetto nei registri.

Pertanto, oltre a dire "l'oggetto è stato creato e anche il campo è presente", non si può dire se qualcosa è nell'heap o nello stack. Si noti che per oggetti piccoli e di breve durata, è possibile che l '"oggetto" non esista nella memoria in quanto tale e possa invece disporre i suoi campi direttamente nei registri.

Il documento si conclude con:

Le JVM sono sorprendentemente brave a capire cose che eravamo soliti presumere che solo lo sviluppatore potesse sapere. Consentendo alla JVM di scegliere tra allocazione di stack e allocazione di heap caso per caso, possiamo ottenere i vantaggi in termini di prestazioni dell'allocazione di stack senza far agonizzare il programmatore se allocare sullo stack o sull'heap.

Pertanto, se si dispone di un codice simile a:

void foo(int arg) {
    Bar qux = new Bar(arg);
    ...
}

dove il ...non consente quxdi lasciare quell'ambito, qux può essere allocato nello stack. Questa è in realtà una vittoria per la VM perché significa che non deve mai essere spazzatura raccolta - scompare quando lascia l'ambito.

Altro su analisi di escape su Wikipedia. Per coloro che desiderano approfondire i lavori, Escape Analysis for Java di IBM. Per coloro che provengono da un mondo C #, potresti trovare le buone letture di Stack Is An Implementation e The Truth About Value di Eric Lippert (sono utili per i tipi Java anche perché molti dei concetti e degli aspetti sono uguali o simili) . Perché i libri .Net parlano dell'allocazione di memoria stack vs heap? anche in questo.

Sul perché della pila e del mucchio

Sul mucchio

Quindi, perché avere lo stack o l'heap? Per le cose che lasciano l'ambito, lo stack può essere costoso. Considera il codice:

void foo(String arg) {
    bar(arg);
    ...
}

void bar(String arg) {
    qux(arg);
    ...
}

void qux(String arg) {
    ...
}

Anche i parametri fanno parte dello stack. Nel caso in cui non si abbia un heap, si passerebbe l'intero set di valori nello stack. Questo va bene per "foo"piccole stringhe ... ma cosa succederebbe se qualcuno inserisse un enorme file XML in quella stringa. Ogni chiamata sarebbe copiare l'intera stringa enorme nello stack - e che sarebbe del tutto uno spreco.

Invece, è meglio mettere gli oggetti che hanno una certa vita al di fuori dell'ambito immediato (passati a un altro ambito, bloccati in una struttura che qualcun altro sta mantenendo, ecc ...) in un'altra area che si chiama heap.

In pila

Non hai bisogno della pila. Si potrebbe ipoteticamente scrivere una lingua che non usa uno stack (di profondità arbitraria). Un vecchio BASIC che ho appreso in gioventù lo ha fatto, si potevano fare solo 8 livelli di gosubchiamate e tutte le variabili erano globali - non c'era stack.

Il vantaggio dello stack è che quando si dispone di una variabile esistente con un ambito, quando si esce da tale ambito, il frame dello stack viene visualizzato. Semplifica davvero cosa c'è e cosa non c'è. Il programma passa a un'altra procedura, un nuovo frame dello stack; il programma ritorna alla procedura e sei tornato a quello che vede il tuo ambito attuale; il programma lascia la procedura e tutti gli oggetti nello stack vengono deallocati.

Questo semplifica davvero la vita alla persona che scrive il runtime per il codice di utilizzare uno stack e un heap. Sono semplicemente molti concetti e modi di lavorare sul codice che consente alla persona che scrive il codice nella lingua di essere liberata dal pensarlo esplicitamente.

La natura dello stack significa anche che non può essere frammentato. La frammentazione della memoria è un vero problema con l'heap. Allocare alcuni oggetti, quindi immondizia ne raccoglie uno centrale e quindi prova a trovare spazio per il prossimo grande da allocare. È un casino. Essere in grado di mettere le cose in pila significa invece che non devi occupartene.

Quando viene raccolta una spazzatura

Quando qualcosa viene raccolta, viene persa. Ma è solo spazzatura raccolta perché è già dimenticata: non ci sono più riferimenti all'oggetto nel programma a cui è possibile accedere dallo stato corrente del programma.

Sottolineerò che questa è una semplificazione molto grande della raccolta dei rifiuti. Esistono molti garbage collector (anche all'interno di Java - puoi modificare il garbage collector utilizzando vari flag ( documenti ). Questi si comportano in modo diverso e le sfumature di come ognuno fa le cose sono un po 'troppo profonde per questa risposta. Potresti voler leggere Nozioni di base di Java Garbage Collection per avere un'idea migliore di come funziona.

Detto questo, se qualcosa viene allocato nello stack, non viene immesso nella spazzatura come parte di System.gc()- viene deallocato quando viene visualizzato il frame dello stack. Se qualcosa si trova nell'heap e fa riferimento a qualcosa nello stack, in quel momento non verrà raccolto.

Perché è importante?

Per la maggior parte, la sua tradizione. I libri di testo scritti e le classi del compilatore e la documentazione di vari bit fanno un grosso problema per l'heap e lo stack.

Tuttavia, le macchine virtuali di oggi (JVM e simili) hanno fatto di tutto per cercare di tenerlo nascosto al programmatore. A meno che tu non stia esaurendo l'uno o l'altro e abbia bisogno di sapere perché (piuttosto che aumentare semplicemente lo spazio di archiviazione in modo appropriato), non importa troppo.

L'oggetto si trova da qualche parte e si trova nel punto in cui è possibile accedervi correttamente e rapidamente per il periodo di tempo appropriato che esiste. Se è in pila o in pila, non importa davvero.


7
  1. Nell'heap, come parte dell'oggetto, a cui fa riferimento un puntatore nello stack. vale a dire. aeb verranno memorizzati uno accanto all'altro.
  2. Perché se tutta la memoria fosse memoria dello stack, non sarebbe più efficiente. È bello avere una piccola area ad accesso rapido da cui iniziare e avere gli elementi di riferimento nell'area molto più grande della memoria che rimane. Tuttavia, questo è eccessivo quando un oggetto è semplicemente una singola primitiva che occuperebbe circa la stessa quantità di spazio nello stack del puntatore ad esso.
  3. Sì.

1
Aggiungerei al tuo punto n. 2 che se hai archiviato oggetti nello stack (immagina un oggetto dizionario con centinaia di migliaia di voci), quindi per passarlo a o restituirlo da una funzione avresti bisogno di copiare l'oggetto ogni tempo. Utilizzando un puntatore o un riferimento a un oggetto nell'heap, passiamo solo il (piccolo) riferimento.
Scott Whitlock,

1
Ho pensato che 3 sarebbe "no" perché se l'oggetto viene raccolto in modo inutile, nello stack non ci sono riferimenti che lo puntino.
Luciano,

@Luciano - Vedo il tuo punto. Ho letto la domanda 3 in modo diverso. "Allo stesso tempo" o "a quel tempo" è implicito. :: scrollata di spalle ::
pdr

3
  1. Sull'heap a meno che Java non alloca l'istanza di classe sullo stack come ottimizzazione dopo aver dimostrato tramite analisi di escape che ciò non influirà sulla semantica. Questo è un dettaglio di implementazione, tuttavia, quindi per tutti gli scopi pratici tranne la micro-ottimizzazione la risposta è "sul mucchio".

  2. La memoria dello stack deve essere allocata e deallocata per ultima nel primo ordine di uscita. La memoria heap può essere allocata e deallocata in qualsiasi ordine.

  3. Quando l'oggetto viene garbage collection, non ci sono più riferimenti che lo puntano dallo stack. Se ci fossero, terrebbero vivo l'oggetto. Le primitive dello stack non sono affatto spazzatura perché vengono automaticamente distrutte al ritorno della funzione.


3

La memoria dello stack viene utilizzata per memorizzare le variabili locali e la chiamata di funzione.

Mentre la memoria heap viene utilizzata per archiviare oggetti in Java. Indipendentemente da dove l'oggetto viene creato nel codice.

Dove sarà il primitivo ain class Aessere conservato?

In questo caso la primitiva a è associata all'oggetto di classe A. Quindi crea nella memoria dell'heap.

Perché esiste la memoria heap? Perché non possiamo archiviare tutto in pila?

  • Le variabili create nello stack non rientrano nell'ambito di applicazione e vengono automaticamente distrutte.
  • Lo stack è molto più veloce da allocare rispetto alle variabili nell'heap.
  • Le variabili nell'heap devono essere distrutte da Garbage Collector.
  • Più lento da allocare rispetto alle variabili nello stack.
  • Utilizzeresti lo stack se sai esattamente quanti dati devi allocare prima del tempo di compilazione e non sono troppo grandi (le variabili locali primitive vengono archiviate nello stack)
  • Utilizzeresti l'heap se non sai esattamente quanti dati ti occorrono in fase di esecuzione o se devi allocare molti dati.

Quando l'oggetto viene raccolto spazzatura, lo stack associato all'oggetto viene distrutto?

Garbage Collector funziona nell'ambito della memoria Heap, quindi distrugge gli oggetti che non hanno una catena di riferimento dalla radice.


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.