Dove risiede il pool di costanti String di Java, l'heap o lo stack?


104

Conosco il concetto di un pool di costanti e il pool di costanti String utilizzato dalle JVM per gestire i letterali String. Ma non so quale tipo di memoria viene utilizzato dalla JVM per memorizzare le costanti stringhe. La pila o il mucchio? Poiché è un valore letterale che non è associato a nessuna istanza, presumo che verrà memorizzato nello stack. Ma se non viene riferito da nessuna istanza, il letterale deve essere raccolto dall'esecuzione di GC (correggimi se sbaglio), quindi come viene gestito se è memorizzato nello stack?


11
Come può essere immagazzinata una piscina nello stack? conosci il concetto di pila?
The Scrum Meister

1
Ciao Scrum Meister, ho cercato di dire che non può essere. Ci scusiamo per la convenzione sbagliata. Per quanto riguarda GC Solo ora sono venuto a sapere. Grazie per questo
Rengasami Ramanujam

@TheScrumMeister - infatti, in alcune circostanze possono essere raccolti in modo spazzatura. L '"interruzione dell'affare" è che l'oggetto codice per qualsiasi classe che menziona un letterale stringa avrà un riferimento all'oggetto String che rappresenta il letterale.
Stephen C

Risposte:


74

La risposta è tecnicamente nessuna delle due. Secondo le specifiche della Java Virtual Machine, l'area per la memorizzazione dei valori letterali stringa si trova nel pool di costanti di runtime . L'area di memoria del pool di costanti di runtime viene allocata in base alla classe o all'interfaccia, quindi non è affatto legata a nessuna istanza di oggetto. Il pool di costanti di runtime è un sottoinsieme dell'area del metodo che "memorizza le strutture per classe come il pool di costanti di runtime, i dati di campo e metodo e il codice per metodi e costruttori, inclusi i metodi speciali usati nell'inizializzazione e nell'interfaccia di classi e istanze inizializzazione del tipo ". La specifica VM dice che sebbene l' area del metodo è logicamente parte dell'heap, non impone che la memoria allocata nell'area del metodo sia soggetta a garbage collection o altri comportamenti che sarebbero associati alle normali strutture di dati allocate all'heap.


8
In realtà, quando le classi vengono caricate nella VM, le costanti di stringa verranno copiate nell'heap, in un pool di stringhe a livello di VM (nel permgen, come ha detto Stephen C), poiché le stringhe letterali uguali in classi diverse devono essere le stesso oggetto String (da JLS).
Paŭlo Ebermann

1
Grazie a tutti per le vostre risposte. Ho capito molto con questa discussione. È bello conoscervi ragazzi :)
Rengasami Ramanujam

4
Paŭlo, questo è vero per la macchina virtuale di Sun, ma non necessariamente per tutte le implementazioni della JVM. Come menzionato dalla specifica JVM, sebbene il pool di costanti di runtime e l'area del metodo facciano parte logicamente dell'heap, non devono avere lo stesso comportamento. Solo una piccola differenza semantica, davvero :)
Duane Moore


54

Come spiegato da questa risposta , la posizione esatta del pool di stringhe non è specificata e può variare da un'implementazione JVM all'altra.

È interessante notare che fino a Java 7, il pool si trovava nello spazio permgen dell'heap su Hotspot JVM ma è stato spostato nella parte principale dell'heap da Java 7 :

Area : HotSpot
Sinossi : In JDK 7, le stringhe internate non vengono più allocate nella generazione permanente dell'heap Java, ma sono invece allocate nella parte principale dell'heap Java (noto come generazione giovane e vecchia), insieme alle altre oggetti creati dall'applicazione. Questa modifica si tradurrà in più dati che risiedono nell'heap Java principale e in meno dati nella generazione permanente, pertanto potrebbe essere necessario regolare le dimensioni dell'heap. La maggior parte delle applicazioni vedrà solo differenze relativamente piccole nell'utilizzo dell'heap a causa di questo cambiamento, ma le applicazioni più grandi che caricano molte classi o fanno un uso pesante del metodo String.intern () vedranno differenze più significative. RFE: 6962931

E in Java 8 Hotspot, la generazione permanente è stata completamente rimossa.


30

I valori letterali stringa non vengono archiviati nello stack. Mai. In effetti, nessun oggetto viene memorizzato nello stack.

I valori letterali stringa (o, più precisamente, gli oggetti String che li rappresentano) sono stati storicamente archiviati in un heap chiamato heap "permgen". (Permgen è l'abbreviazione di generazione permanente.)

In circostanze normali, i valori letterali String e gran parte delle altre cose nell'heap permgen sono "permanentemente" raggiungibili e non vengono raccolti in modo indesiderato. (Ad esempio, i valori letterali stringa sono sempre raggiungibili dagli oggetti codice che li utilizzano.) Tuttavia, è possibile configurare una JVM per tentare di trovare e raccogliere classi caricate dinamicamente che non sono più necessarie e ciò potrebbe causare la raccolta di dati obsoleti di stringhe letterali .

CHIARIMENTO # 1 - Non sto dicendo che Permgen non ottiene GC'ed. Lo fa, in genere quando la JVM decide di eseguire un GC completo. Il punto è che i letterali String saranno raggiungibili fintanto che il codice che li usa è raggiungibile, e il codice sarà raggiungibile fintanto che il classloader del codice è raggiungibile, e per i classloader predefiniti, ciò significa "per sempre".

CHIARIMENTO # 2 - In effetti, Java 7 e versioni successive utilizzano l'heap normale per contenere il pool di stringhe. Pertanto, gli oggetti String che rappresentano i valori letterali String e le stringhe interne si trovano effettivamente nell'heap normale. (Vedi la risposta di @ assylias per i dettagli.)


Ma sto ancora cercando di scoprire una linea sottile tra la memorizzazione della stringa letterale e la stringa creata con new.

Non esiste una "linea sottile". È davvero molto semplice:

  • String gli oggetti che rappresentano / corrispondono a stringhe letterali vengono conservati nel pool di stringhe.
  • Stringgli oggetti creati da una String::internchiamata vengono conservati nel pool di stringhe.
  • Tutti gli altri Stringoggetti NON sono contenuti nel pool di stringhe.

Poi c'è la questione separata di dove è "memorizzato" il pool di stringhe. Prima di Java 7 era l'heap permgen. Da Java 7 in poi è l'heap principale.


23

Pool di stringhe

Il pool di stringhe (a volte chiamato anche canonicalizzazione delle stringhe) è un processo di sostituzione di più oggetti String con lo stesso valore ma identità diversa con un singolo oggetto String condiviso. Puoi raggiungere questo obiettivo mantenendo la tua mappa (con riferimenti possibilmente morbidi o deboli a seconda delle tue esigenze) e utilizzando i valori della mappa come valori canonizzati. Oppure puoi usare il metodo String.intern () che ti viene fornito da JDK.

A volte di Java 6 l'utilizzo di String.intern () era vietato da molti standard a causa dell'elevata possibilità di ottenere un'eccezione OutOfMemoryException se il pooling andava fuori controllo. L'implementazione di Oracle Java 7 del pool di stringhe è stata notevolmente modificata. Puoi cercare i dettagli in http://bugs.sun.com/view_bug.do?bug_id=6962931 e http://bugs.sun.com/view_bug.do?bug_id=6962930 .

String.intern () in Java 6

In quei bei vecchi tempi tutte le stringhe internate erano memorizzate in PermGen - la parte di dimensione fissa dell'heap utilizzata principalmente per memorizzare le classi caricate e il pool di stringhe. Oltre alle stringhe internate in modo esplicito, il pool di stringhe PermGen conteneva anche tutte le stringhe letterali precedentemente utilizzate nel programma (viene utilizzata la parola importante qui - se una classe o un metodo non è mai stato caricato / chiamato, le costanti definite in esso non verranno caricate).

Il problema più grande con tale pool di stringhe in Java 6 era la sua posizione: PermGen. PermGen ha una dimensione fissa e non può essere espansa in fase di esecuzione. Puoi impostarlo utilizzando l'opzione -XX: MaxPermSize = 96m. Per quanto ne so, la dimensione predefinita di PermGen varia tra 32M e 96M a seconda della piattaforma. Puoi aumentare le sue dimensioni, ma le sue dimensioni saranno comunque fisse. Tale limitazione richiedeva un uso molto attento di String.intern: è meglio non internare alcun input utente non controllato utilizzando questo metodo. Ecco perché il pool di stringhe a volte di Java 6 era per lo più implementato nelle mappe gestite manualmente.

String.intern () in Java 7

Gli ingegneri Oracle hanno apportato una modifica estremamente importante alla logica del pool di stringhe in Java 7: il pool di stringhe è stato riposizionato nell'heap. Significa che non sei più limitato da un'area di memoria di dimensioni fisse separata. Tutte le stringhe si trovano ora nell'heap, come la maggior parte degli altri oggetti ordinari, il che consente di gestire solo la dimensione dell'heap durante l'ottimizzazione dell'applicazione. Tecnicamente, questo da solo potrebbe essere un motivo sufficiente per riconsiderare l'utilizzo di String.intern () nei programmi Java 7. Ma ci sono altri motivi.

I valori del pool di stringhe vengono raccolti in modo obsoleto

Sì, tutte le stringhe nel pool di stringhe JVM sono idonee per la garbage collection se non ci sono riferimenti ad esse dalle radici del programma. Si applica a tutte le versioni discusse di Java. Significa che se la tua stringa internata è uscita dall'ambito e non ci sono altri riferimenti ad essa, verrà raccolta in modo indesiderato dal pool di stringhe JVM.

Essendo idoneo per la garbage collection e risiedendo nell'heap, un pool di stringhe JVM sembra essere il posto giusto per tutte le tue stringhe, non è vero? In teoria è vero: le stringhe non utilizzate verranno raccolte in modo indesiderato dal pool, le stringhe utilizzate consentiranno di risparmiare memoria nel caso in cui si ottenga una stringa uguale dall'input. Sembra essere una perfetta strategia di risparmio della memoria? Quasi così. È necessario sapere come viene implementato il pool di stringhe prima di prendere qualsiasi decisione.

fonte.


11

Come spiegano altre risposte, la memoria in Java è divisa in due parti

1. Stack: viene creato uno stack per thread e memorizza gli stack frame che di nuovo memorizzano le variabili locali e se una variabile è un tipo di riferimento, quella variabile si riferisce a una posizione di memoria nell'heap per l'oggetto effettivo.

2. Heap: tutti i tipi di oggetti verranno creati solo in heap.

La memoria heap viene nuovamente suddivisa in 3 porzioni

1. Young Generation: Memorizza oggetti che hanno una vita breve, Young Generation stessa può essere divisa in due categorie Eden Space e Survivor Space .

2. Vecchia generazione: archivia gli oggetti che sono sopravvissuti a molti cicli di raccolta dei rifiuti ea cui si fa ancora riferimento.

3. Generazione permanente: memorizza i metadati sul programma, ad esempio il pool di costanti di runtime.

Il pool di costanti di stringa appartiene all'area di generazione permanente della memoria Heap.

Possiamo vedere il pool di costanti di runtime per il nostro codice nel bytecode usando javap -verbose class_nameche ci mostrerà i riferimenti ai metodi (#Methodref), gli oggetti Class (#Class), i valori letterali di stringa (#String)

runtime-costante-piscina

Puoi leggere di più al riguardo nel mio articolo In che modo JVM gestisce internamente l'overload e l'override del metodo .


Si prega di divulgare eventuali affiliazioni e di non utilizzare il sito come mezzo per promuovere il proprio sito tramite la pubblicazione. Vedi Come scrivo una buona risposta? .

9

Alle grandi risposte già incluse qui voglio aggiungere qualcosa che manca nella mia prospettiva: un'illustrazione.

Poiché già JVM divide la memoria allocata a un programma Java in due parti. uno è stack e un altro è heap . Lo stack viene utilizzato a scopo di esecuzione e l'heap viene utilizzato a scopo di archiviazione. In quella memoria heap, JVM alloca parte della memoria appositamente pensata per i valori letterali di stringa. Questa parte della memoria heap è denominata pool di costanti di stringa .

Quindi, ad esempio, se inizi i seguenti oggetti:

String s1 = "abc"; 
String s2 = "123";
String obj1 = new String("abc");
String obj2 = new String("def");
String obj3 = new String("456);

Stringhe letterali s1e s2andranno alla stringa costante pool, gli oggetti obj1, obj2, obj3 all'heap. Tutti saranno referenziati dallo Stack.

Inoltre, tieni presente che "abc" apparirà nell'heap e nel pool di costanti di stringa. Perché è String s1 = "abc"e String obj1 = new String("abc")sarà creato in questo modo? È perché String obj1 = new String("abc")crea in modo esplicito un'istanza nuova e referenzialmente distinta di un oggetto String e String s1 = "abc"può riutilizzare un'istanza dal pool di costanti di stringa, se disponibile. Per una spiegazione più elaborata: https://stackoverflow.com/a/3298542/2811258

inserisci qui la descrizione dell'immagine


Nel diagramma dato, dove esisterebbero i letterali "def" e "456". E come si farebbero riferimento a questi?
Satyendra

Grazie per il tuo commento @Satyendra, ho aggiornato l'illustrazione e la risposta.
Johnny

@Stas perché viene creato un altro oggetto String "abc "..dovrebbe usare il riferimento obj1 per puntare il letterale a destra?

È perché String obj1 = new String ("abc") crea esplicitamente una nuova istanza referenzialmente distinta di un oggetto String e String s1 = "abc" può riutilizzare un'istanza dal pool di costanti di stringa se disponibile. Per una spiegazione più elaborata: stackoverflow.com/a/3298542/2811258
Johnny
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.