Wo befindet sich Javas String-Konstantenpool, der Heap oder der Stack?


103

Ich kenne das Konzept eines Konstantenpools und des String-Konstantenpools, die von JVMs zum Behandeln von String-Literalen verwendet werden. Ich weiß jedoch nicht, welcher Speichertyp von der JVM zum Speichern von konstanten String-Literalen verwendet wird. Der Stapel oder der Haufen? Da es sich um ein Literal handelt, das keiner Instanz zugeordnet ist, würde ich davon ausgehen, dass es im Stapel gespeichert wird. Aber wenn es von keiner Instanz referenziert wird, muss das Literal durch GC-Lauf gesammelt werden (korrigieren Sie mich, wenn ich falsch liege). Wie wird das gehandhabt, wenn es im Stapel gespeichert ist?


11
Wie kann ein Pool auf dem Stapel gespeichert werden? Kennen Sie das Konzept eines Stapels?
Der Scrum Meister

1
Hallo Scrum Meister, ich habe versucht zu meinen, dass es nicht sein kann. Entschuldigung für die falsche Konvention. In Bezug auf GC Gerade habe ich es erfahren. Danke dafür
Rengasami Ramanujam

@TheScrumMeister - in der Tat, unter Umständen sie kann Müll gesammelt. Der "Deal Breaker" ist, dass das Codeobjekt für jede Klasse, die ein String-Literal erwähnt, einen Verweis auf das String-Objekt hat, das das Literal darstellt.
Stephen C

Antworten:


74

Die Antwort ist technisch weder. Gemäß der Java Virtual Machine-Spezifikation befindet sich der Bereich zum Speichern von Zeichenfolgenliteralen im Laufzeitkonstantenpool . Der Speicherbereich des Laufzeitkonstantenpools wird pro Klasse oder pro Schnittstelle zugewiesen, sodass er überhaupt nicht an Objektinstanzen gebunden ist. Der Laufzeitkonstantenpool ist eine Teilmenge des Methodenbereichs, in dem "Strukturen pro Klasse wie der Laufzeitkonstantenpool, Feld- und Methodendaten sowie der Code für Methoden und Konstruktoren gespeichert werden, einschließlich der speziellen Methoden, die bei der Klassen- und Instanzinitialisierung und -schnittstelle verwendet werden Typinitialisierung ". Die VM-Spezifikation besagt, dass zwar der Methodenbereich ist logisch Teil des Heaps und schreibt nicht vor, dass der im Methodenbereich zugewiesene Speicher einer Speicherbereinigung oder anderen Verhaltensweisen unterliegt, die mit normalen Datenstrukturen verbunden wären, die dem Heap zugewiesen sind.


8
Wenn die Klassen in die VM geladen werden, werden die Zeichenfolgenkonstanten in den Heap kopiert, in einen VM-weiten Zeichenfolgenpool (im Permgen, wie Stephen C sagte), da gleiche Zeichenfolgenliterale in verschiedenen Klassen die sein müssen gleiches String-Objekt (vom JLS).
Paŭlo Ebermann

1
Vielen Dank für Ihre Antworten. Ich habe mit dieser Diskussion viel verstanden. Es ist schön euch zu kennen :)
Rengasami Ramanujam

4
Paŭlo, das gilt für die virtuelle Maschine von Sun, aber nicht unbedingt für alle Implementierungen der JVM. Wie in der JVM-Spezifikation erwähnt, müssen der Pool der Laufzeitkonstanten und der Methodenbereich logischerweise Teil des Heaps sein, sie müssen jedoch nicht dasselbe Verhalten aufweisen. Nur ein kleiner semantischer Unterschied, wirklich :)
Duane Moore


54

Wie in dieser Antwort erläutert , ist der genaue Speicherort des Zeichenfolgenpools nicht angegeben und kann von JVM-Implementierung zu JVM-Implementierung variieren.

Es ist interessant festzustellen, dass sich der Pool bis Java 7 im Permgen-Bereich des Heaps auf der Hotspot-JVM befand, aber seit Java 7 in den Hauptteil des Heaps verschoben wurde :

Bereich : HotSpot
Synopsis : In JDK 7 werden internierte Zeichenfolgen nicht mehr in der permanenten Generation des Java-Heaps zugewiesen , sondern im Hauptteil des Java-Heaps (bekannt als junge und alte Generation) zusammen mit der anderen von der Anwendung erstellte Objekte. Diese Änderung führt dazu, dass sich mehr Daten im Haupt-Java-Heap und weniger Daten in der permanenten Generierung befinden. Daher müssen möglicherweise die Heap-Größen angepasst werden. Die meisten Anwendungen sehen aufgrund dieser Änderung nur relativ kleine Unterschiede in der Heap-Nutzung, aber größere Anwendungen, die viele Klassen laden oder die String.intern () -Methode stark nutzen, werden größere Unterschiede feststellen. RFE: 6962931

In Java 8 Hotspot wurde die permanente Generierung vollständig entfernt.


30

String-Literale werden nicht im Stapel gespeichert. Noch nie. Tatsächlich werden keine Objekte auf dem Stapel gespeichert.

Stringliterale (oder genauer gesagt, die String - Objekte , die sie vertreten) sind historisch in einem Heap gespeichert wurden genannt die „PermGen“ Haufen. (Permgen steht für permanente Erzeugung.)

Unter normalen Umständen sind String-Literale und viele andere Dinge im Permgen-Heap "permanent" erreichbar und werden nicht durch Müll gesammelt. (Beispielsweise sind String-Literale immer über die Codeobjekte erreichbar, die sie verwenden.) Sie können jedoch eine JVM so konfigurieren, dass versucht wird, dynamisch geladene Klassen zu finden und zu sammeln, die nicht mehr benötigt werden. Dies kann dazu führen, dass String-Literale durch Müll gesammelt werden .

ERKLÄRUNG 1 - Ich sage nicht, dass Permgen nicht GC'ed wird. Dies ist normalerweise der Fall, wenn die JVM beschließt, einen vollständigen GC auszuführen. Mein Punkt ist, dass String- Literale erreichbar sind, solange der Code, der sie verwendet, erreichbar ist, und der Code erreichbar ist, solange der Klassenlader des Codes erreichbar ist, und für die Standard-Klassenlader bedeutet dies "für immer".

CLARIFICATION # 2 - Tatsächlich verwendet Java 7 und höher den regulären Heap, um den Zeichenfolgenpool zu speichern. Daher befinden sich String-Objekte, die String-Literale und interne Zeichenfolgen darstellen, tatsächlich im regulären Heap. (Einzelheiten finden Sie in der Antwort von @ assylias.)


Aber ich versuche immer noch, eine dünne Linie zwischen der Speicherung des String-Literal und dem mit erstellten String herauszufinden new.

Es gibt keine "dünne Linie". Es ist wirklich sehr einfach:

  • String Objekte, die Zeichenfolgenliterale darstellen / diesen entsprechen, werden im Zeichenfolgenpool gespeichert.
  • StringObjekte, die durch einen String::internAufruf erstellt wurden , werden im Zeichenfolgenpool gespeichert.
  • Alle anderen StringObjekte werden NICHT im String-Pool gespeichert.

Dann gibt es die separate Frage, wo der String-Pool "gespeichert" ist. Vor Java 7 war es der Permgen-Heap. Ab Java 7 ist es der Haupthaufen.


23

String-Pooling

Beim String-Pooling (manchmal auch als String-Kanonisierung bezeichnet) werden mehrere String-Objekte mit gleichem Wert, aber unterschiedlicher Identität durch ein einzelnes gemeinsames String-Objekt ersetzt. Sie können dieses Ziel erreichen, indem Sie Ihre eigene Karte (mit möglicherweise weichen oder schwachen Referenzen, abhängig von Ihren Anforderungen) beibehalten und Kartenwerte als kanonisierte Werte verwenden. Oder Sie können die String.intern () -Methode verwenden, die Ihnen von JDK zur Verfügung gestellt wird.

Zu Zeiten von Java 6 war die Verwendung von String.intern () nach vielen Standards aufgrund der hohen Wahrscheinlichkeit, eine OutOfMemoryException zu erhalten, wenn das Pooling außer Kontrolle geriet, verboten. Die Oracle Java 7-Implementierung von String Pooling wurde erheblich geändert. Weitere Informationen finden Sie unter http://bugs.sun.com/view_bug.do?bug_id=6962931 und http://bugs.sun.com/view_bug.do?bug_id=6962930 .

String.intern () in Java 6

In jenen guten alten Zeiten wurden alle internierten Zeichenfolgen im PermGen gespeichert - dem Teil mit fester Größe des Heapspeichers, der hauptsächlich zum Speichern geladener Klassen und des Zeichenfolgenpools verwendet wird. Neben explizit internierten Zeichenfolgen enthielt der PermGen-Zeichenfolgenpool auch alle zuvor in Ihrem Programm verwendeten Literalzeichenfolgen (das wichtige Wort hier wird verwendet - wenn eine Klasse oder Methode nie geladen / aufgerufen wurde, werden darin definierte Konstanten nicht geladen).

Das größte Problem mit einem solchen String-Pool in Java 6 war sein Standort - der PermGen. PermGen hat eine feste Größe und kann zur Laufzeit nicht erweitert werden. Sie können es mit der Option -XX: MaxPermSize = 96m einstellen. Soweit ich weiß, variiert die Standardgröße für PermGen je nach Plattform zwischen 32 und 96 MB. Sie können die Größe erhöhen, die Größe wird jedoch weiterhin festgelegt. Eine solche Einschränkung erforderte eine sehr sorgfältige Verwendung von String.intern - Sie sollten mit dieser Methode keine unkontrollierten Benutzereingaben internieren. Aus diesem Grund wurde das String-Pooling zu Zeiten von Java 6 hauptsächlich in manuell verwalteten Maps implementiert.

String.intern () in Java 7

Die Oracle-Ingenieure haben in Java 7 eine äußerst wichtige Änderung an der String-Pooling-Logik vorgenommen - der String-Pool wurde auf den Heap verschoben. Dies bedeutet, dass Sie nicht mehr durch einen separaten Speicherbereich mit fester Größe eingeschränkt sind. Alle Zeichenfolgen befinden sich jetzt wie die meisten anderen normalen Objekte im Heap, sodass Sie beim Optimieren Ihrer Anwendung nur die Heap-Größe verwalten können. Technisch gesehen könnte dies allein ein ausreichender Grund sein, die Verwendung von String.intern () in Ihren Java 7-Programmen zu überdenken. Es gibt aber noch andere Gründe.

String-Pool-Werte werden durch Müll gesammelt

Ja, alle Zeichenfolgen im JVM-Zeichenfolgenpool können für die Speicherbereinigung verwendet werden, wenn Ihre Programmwurzeln keine Verweise darauf enthalten. Dies gilt für alle besprochenen Versionen von Java. Wenn Ihre internierte Zeichenfolge den Gültigkeitsbereich verlässt und keine anderen Verweise darauf vorhanden sind, handelt es sich um Müll, der aus dem JVM-Zeichenfolgenpool gesammelt wird.

Ein JVM-String-Pool, der für die Speicherbereinigung in Frage kommt und sich auf dem Heap befindet, scheint der richtige Ort für alle Ihre Strings zu sein, nicht wahr? Theoretisch ist es wahr - nicht verwendete Zeichenfolgen werden aus dem Pool gesammelt. Mit verwendeten Zeichenfolgen können Sie Speicherplatz sparen, falls Sie von der Eingabe eine gleiche Zeichenfolge erhalten. Scheint eine perfekte Strategie zum Speichern von Speicher zu sein? Fast so. Sie müssen wissen, wie der Zeichenfolgenpool implementiert ist, bevor Sie Entscheidungen treffen.

Quelle.


11

Wie andere Antworten erklären, ist der Speicher in Java in zwei Teile unterteilt

1. Stapel: Pro Thread wird ein Stapel erstellt, in dem Stapelrahmen gespeichert werden, in denen wiederum lokale Variablen gespeichert werden. Wenn eine Variable ein Referenztyp ist, verweist diese Variable auf einen Speicherort im Heap für das tatsächliche Objekt.

2. Heap: Alle Arten von Objekten werden nur im Heap erstellt.

Der Heap-Speicher ist wieder in 3 Teile unterteilt

1. Junge Generation: Speichert Objekte mit kurzer Lebensdauer. Die junge Generation selbst kann in zwei Kategorien unterteilt werden: Eden Space und Survivor Space .

2. Alte Generation: Speichern Sie Objekte, die viele Speicherbereinigungszyklen überstanden haben und auf die noch verwiesen wird.

3. Permanente Generierung: Speichert Metadaten über das Programm, z. B. den Laufzeitkonstantenpool.

Der String-Konstantenpool gehört zum permanenten Generierungsbereich des Heap-Speichers.

Wir können den Laufzeitkonstantenpool für unseren Code im Bytecode sehen, indem javap -verbose class_namewir Methodenreferenzen (#Methodref), Klassenobjekte (#Class), Zeichenfolgenliterale (#String) anzeigen.

Laufzeit-Konstanten-Pool

Sie können mehr darüber auf meinen Artikel lesen Wie funktioniert JVM Handle Methode Overloading und Übergeordnete Intern .


Bitte legen Sie jegliche Zugehörigkeit offen und verwenden Sie die Website nicht, um Ihre Website durch Veröffentlichung zu bewerben. Siehe Wie schreibe ich eine gute Antwort? .

9

Zu den großartigen Antworten, die hier bereits enthalten sind, möchte ich etwas hinzufügen, das in meiner Perspektive fehlt - eine Illustration.

Wie Sie bereits teilen, teilt JVM den zugewiesenen Speicher eines Java-Programms in zwei Teile. einer ist Stapel und ein anderer ist Haufen . Der Stapel wird für Ausführungszwecke und der Heap für Speicherzwecke verwendet. In diesem Heapspeicher weist JVM Speicher zu, der speziell für Zeichenfolgenliterale vorgesehen ist. Dieser Teil des Heapspeichers wird als String-Konstanten-Pool bezeichnet .

Wenn Sie beispielsweise die folgenden Objekte initiieren:

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

String-Literale s1und s2gehen zum String-Konstanten-Pool, Objekte obj1, obj2, obj3 zum Heap. Alle von ihnen werden aus dem Stapel referenziert.

Beachten Sie außerdem, dass "abc" im Heap- und im String-Konstantenpool angezeigt wird. Warum wird String s1 = "abc"und String obj1 = new String("abc")wird so geschaffen? Dies liegt daran, dass String obj1 = new String("abc")explizit eine neue und referenziell unterschiedliche Instanz eines String-Objekts erstellt wird und String s1 = "abc"möglicherweise eine Instanz aus dem String-Konstantenpool wiederverwendet wird, sofern eine verfügbar ist. Für eine ausführlichere Erklärung: https://stackoverflow.com/a/3298542/2811258

Geben Sie hier die Bildbeschreibung ein


Wo würden in dem gegebenen Diagramm die Literale "def" und "456" existieren? Und wie würde darauf verwiesen werden?
Satyendra

Vielen Dank für Ihren Kommentar @Satyendra, ich habe die Abbildung und die Antwort aktualisiert.
Johnny

@Stas, warum ein anderes String-Objekt "abc" erstellt wird. Es sollte die Referenz obj1 verwenden, um das Literalrecht zu verweisen.

Dies liegt daran, dass String obj1 = new String ("abc") explizit eine neue und referenziell unterschiedliche Instanz eines String-Objekts erstellt und String s1 = "abc" eine Instanz aus dem String-Konstantenpool wiederverwenden kann, falls eine verfügbar ist. Für eine ausführlichere Erklärung: stackoverflow.com/a/3298542/2811258
Johnny
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.