Stapel- und Heapspeicher in Java


99

Wie ich verstehe, enthält der Stapelspeicher in Java Primitive und Methodenaufrufe, und der Heapspeicher wird zum Speichern von Objekten verwendet.

Angenommen, ich habe eine Klasse

class A {
       int a ;
       String b;
       //getters and setters
}
  1. Wo wird das Primitiv ain der Klasse Aaufbewahrt?

  2. Warum gibt es überhaupt Heapspeicher? Warum können wir nicht alles auf dem Stapel speichern?

  3. Wenn das Objekt Müll sammelt, wird der dem Objekt zugeordnete Stapel zerstört?



@ S.Lott, außer dass es sich um Java handelt, nicht um C.
Péter Török

@ Péter Török: Einverstanden. Während das Codebeispiel Java ist, gibt es kein Tag, das angibt, dass es nur Java ist. Und das allgemeine Prinzip sollte sowohl für Java als auch für C gelten. Außerdem gibt es viele Antworten auf diese Frage zum Stapelüberlauf.
S.Lott

9
@SteveHaigh: Auf dieser Site ist jeder viel zu besorgt, ob hier etwas hingehört ... Ich frage mich, was diese Site wirklich an Geistesblicken gewinnt, wenn man bedenkt, ob Fragen hierher gehören oder nicht.
Sam Goldberg

Antworten:


106

Der grundlegende Unterschied zwischen Stack und Heap ist der Lebenszyklus der Werte.

Stapelwerte existieren nur im Rahmen der Funktion, in der sie erstellt wurden. Sobald sie zurückgegeben werden, werden sie verworfen.
Auf dem Heap sind jedoch Heap-Werte vorhanden. Sie werden zu einem bestimmten Zeitpunkt erstellt und zu einem anderen Zeitpunkt zerstört (entweder durch GC oder manuell, je nach Sprache / Laufzeit).

Jetzt speichert Java nur noch Primitive auf dem Stack. Dies hält den Stapel klein und hilft dabei, einzelne Stapelrahmen klein zu halten, wodurch mehr verschachtelte Aufrufe möglich sind.
Objekte werden auf dem Heap erstellt, und nur Verweise (die wiederum Grundelemente sind) werden auf dem Stapel herumgereicht.

Wenn Sie also ein Objekt erstellen, wird es mit allen dazugehörigen Variablen auf den Heap gelegt, damit es nach der Rückkehr des Funktionsaufrufs bestehen bleibt.


2
"und nur Referenzen (die wiederum Primitive sind)" Warum sagen Sie, dass Referenzen Primitive sind? Können Sie bitte klären?
Geek

5
@Geek: Da die allgemeine Definition von primitiven Datentypen gilt: "Ein Datentyp, der von einer Programmiersprache als Grundbaustein bereitgestellt wird" . Möglicherweise stellen Sie auch fest, dass Verweise unter den kanonischen Beispielen weiter unten im Artikel aufgeführt sind .
back2dos

4
@Geek: In Bezug auf Daten können Sie alle primitiven Datentypen - einschließlich Referenzen - als Zahlen anzeigen. Sogar chars sind Zahlen und können als solche synonym verwendet werden. Referenzen sind auch nur Zahlen, die sich auf eine Speicheradresse beziehen, die entweder 32 oder 64 Bit lang ist (obwohl sie nicht als solche verwendet werden können - es sei denn, Sie spielen herum sun.misc.Unsafe).
Sune Rasmussen

3
Die Terminologie dieser Antwort ist falsch. Nach der Java-Sprachspezifikation sind Referenzen KEINE Grundelemente. Der Kern dessen, was die Antwort sagt, ist jedoch richtig. (Während Sie ein Argument machen , dass Verweise „in gewissem Sinne“ primitiv ist durch die von der JLS die Terminologie für Java definiert, und es sagt , dass die primitiven Typen sind. boolean, byte, short, char, int, long, floatUnd double.)
Stephen C

4
Wie ich in diesem Artikel festgestellt habe , speichert Java möglicherweise Objekte auf dem Stapel (oder sogar in Registern für kleine kurzlebige Objekte). Die JVM kann im Verborgenen einiges leisten. Es ist nicht genau richtig zu sagen "Jetzt speichert Java nur Primitive auf dem Stack."

49

Wo werden primitive Felder gespeichert?

Primitive Felder werden als Teil des Objekts gespeichert, das irgendwo instanziiert wird . Der einfachste Weg, sich vorzustellen, wo dies ist, ist der Haufen. Dies ist jedoch nicht immer der Fall. Wie in Java-Theorie und -Praxis beschrieben: Legenden zur urbanen Performance, überarbeitet :

JVMs können eine Technik namens Escape-Analyse verwenden, mit der sie feststellen können, dass bestimmte Objekte während ihrer gesamten Lebensdauer auf einen einzelnen Thread beschränkt bleiben und dass die Lebensdauer durch die Lebensdauer eines bestimmten Stack-Frames begrenzt ist. Solche Objekte können sicher auf dem Stapel anstelle des Heaps zugeordnet werden. Noch besser ist, dass die JVM für kleine Objekte die Zuordnung vollständig optimieren und die Felder des Objekts einfach in Register hochziehen kann.

Somit kann man nicht sagen, dass "das Objekt erstellt wurde und das Feld auch vorhanden ist", sondern ob sich etwas auf dem Haufen oder auf dem Stapel befindet. Beachten Sie, dass es bei kleinen, kurzlebigen Objekten möglich ist, dass das 'Objekt' nicht als solches im Speicher vorhanden ist und stattdessen die Felder direkt in Registern abgelegt werden.

Das Papier schließt mit:

JVMs sind erstaunlich gut darin, Dinge herauszufinden, von denen wir bisher angenommen haben, dass nur der Entwickler sie wissen könnte. Wenn die JVM von Fall zu Fall zwischen Stapelzuweisung und Heapzuweisung wählen kann, können wir die Leistungsvorteile der Stapelzuweisung nutzen, ohne dass sich der Programmierer darüber ärgert, ob auf dem Stapel oder auf dem Heap zuzuweisen ist.

Wenn Sie also Code haben, der so aussieht:

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

wo das ...nicht zulässt qux, diesen Bereich zu verlassen, qux kann stattdessen auf dem Stapel zugewiesen werden. Dies ist tatsächlich ein Gewinn für die VM, da dies bedeutet, dass niemals Müll eingesammelt werden muss - er verschwindet, wenn er den Gültigkeitsbereich verlässt.

Mehr zur Fluchtanalyse bei Wikipedia. Für diejenigen, die bereit sind, sich mit Artikeln zu beschäftigen, bietet Escape Analysis für Java von IBM. Für diejenigen, die aus einer C # -Welt kommen, ist The Stack ein Implementierungsdetail und die Wahrheit über Werttypen von Eric Lippert eine gute Lektüre (sie sind auch für Java-Typen nützlich, da viele der Konzepte und Aspekte gleich oder ähnlich sind). . Warum geht es in .Net-Büchern um die Zuordnung von Stapel- und Heapspeicher? geht auch darauf ein.

Auf dem Wieso des Stapels und des Haufens

Auf dem Haufen

Warum also überhaupt den Stapel oder den Haufen? Für Dinge, die den Spielraum verlassen, kann der Stapel teuer sein. Betrachten Sie den Code:

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

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

void qux(String arg) {
    ...
}

Die Parameter sind ebenfalls Teil des Stacks. In der Situation, dass Sie keinen Heap haben, übergeben Sie den vollständigen Satz von Werten auf dem Stapel. Dies ist in Ordnung für "foo"und kleine Zeichenfolgen ... aber was würde passieren, wenn jemand eine große XML-Datei in diese Zeichenfolge einfügen würde. Jeder Aufruf würde die gesamte riesige Zeichenfolge auf den Stapel kopieren - und das wäre ziemlich verschwenderisch.

Stattdessen ist es besser, die Objekte, deren Leben außerhalb des unmittelbaren Bereichs liegt (an einen anderen Bereich übergeben, in einer von einem anderen verwalteten Struktur festgehalten, usw.), in einen anderen Bereich zu verschieben, der als Haufen bezeichnet wird.

Auf dem Stapel

Sie brauchen den Stapel nicht. Man könnte hypothetisch eine Sprache schreiben, die keinen Stapel (von beliebiger Tiefe) verwendet. Ein altes BASIC, auf dem ich in meiner Jugend gelernt habe, hat dies getan, man konnte nur 8 Ebenen von gosubAufrufen ausführen und alle Variablen waren global - es gab keinen Stapel.

Der Vorteil des Stapels besteht darin, dass bei Verwendung einer Variablen, die mit einem Bereich existiert, beim Verlassen dieses Bereichs dieser Stapelrahmen eingeblendet wird. Es vereinfacht wirklich, was da ist und was nicht. Das Programm wechselt zu einer anderen Prozedur, einem neuen Stapelrahmen. Das Programm kehrt zur Prozedur zurück, und Sie befinden sich wieder in dem Bereich, in dem Ihr aktueller Bereich angezeigt wird. Das Programm verlässt die Prozedur und alle Elemente auf dem Stapel werden freigegeben.

Dies erleichtert der Person das Schreiben der Laufzeit für den Code, um einen Stapel und einen Haufen zu verwenden. Sie sind einfach viele Konzepte und Arbeitsweisen am Code, die es der Person, die den Code in der Sprache schreibt, ermöglichen, sich frei zu machen, explizit an sie zu denken.

Die Natur des Stapels bedeutet auch, dass er nicht fragmentiert werden kann. Speicherfragmentierung ist ein echtes Problem mit dem Heap. Sie ordnen ein paar Objekte zu, sammeln dann ein mittleres und versuchen dann, Platz für das nächste große Objekt zu finden, das zugewiesen werden soll. Es ist ein Chaos. In der Lage zu sein, Dinge auf den Stapel zu legen, bedeutet, dass Sie sich nicht darum kümmern müssen.

Wenn etwas Müll gesammelt wird

Wenn etwas Müll ist, ist es weg. Aber es wird nur Müll gesammelt, weil es bereits vergessen wurde - es gibt keine Referenzen mehr auf das Objekt im Programm, auf die vom aktuellen Status des Programms aus zugegriffen werden kann.

Ich werde darauf hinweisen, dass dies eine sehr große Vereinfachung der Speicherbereinigung ist. Es gibt viele Garbage Collectors (sogar in Java - Sie können den Garbage Collector mithilfe verschiedener Flags ( Dokumente ) optimieren . Diese verhalten sich unterschiedlich und die Nuancen der einzelnen Aktionen sind für diese Antwort etwas zu tief. Sie möchten möglicherweise lesen Grundlagen der Java Garbage Collection , um eine bessere Vorstellung davon zu bekommen, wie ein Teil davon funktioniert.

Das heißt, wenn etwas auf dem Stapel zugewiesen ist, wird es nicht als Teil von Garbage System.gc()Collected freigegeben, wenn der Stapelrahmen aufspringt. Wenn sich etwas auf dem Haufen befindet und von etwas auf dem Stapel referenziert wird, wird zu diesem Zeitpunkt kein Müll gesammelt.

Warum ist das wichtig?

Zum größten Teil seine Tradition. Die geschriebenen Lehrbücher und Compiler-Klassen sowie die verschiedenen Bit-Dokumentationen machen eine große Sache über den Haufen und den Stapel.

Die heutigen virtuellen Maschinen (JVM und ähnliche) haben jedoch große Anstrengungen unternommen, um dies vor dem Programmierer zu verbergen. Es spielt keine große Rolle, es sei denn, Sie haben das eine oder andere Problem und müssen wissen, warum (anstatt nur den Speicherplatz entsprechend zu vergrößern).

Das Objekt befindet sich irgendwo und seine in dem Ort , wo es richtig und schnell für die entsprechende Menge an Zeit zugegriffen werden kann , dass es existiert. Ob auf dem Stapel oder auf dem Haufen - es spielt keine Rolle.


7
  1. Im Heap als Teil des Objekts, auf das durch einen Zeiger im Stapel verwiesen wird. dh a und b werden nebeneinander gespeichert.
  2. Denn wenn der gesamte Speicher ein Stapelspeicher wäre, wäre er nicht mehr effizient. Es ist gut, einen kleinen Bereich mit schnellem Zugriff zu haben, in dem wir beginnen, und diese Referenzelemente in dem viel größeren Bereich des verbleibenden Speichers zu haben. Dies ist jedoch ein Overkill, wenn ein Objekt einfach ein einzelnes Grundelement ist, das ungefähr den gleichen Platz auf dem Stapel beansprucht wie der Zeiger darauf.
  3. Ja.

1
Ich möchte zu Punkt 2 hinzufügen, dass Sie, wenn Sie Objekte auf dem Stapel speichern (stellen Sie sich ein Dictionary-Objekt mit Hunderttausenden von Einträgen vor), das Objekt jedes Mal kopieren müssen, um es an eine Funktion zu übergeben oder von dieser zurückzugeben Zeit. Durch die Verwendung eines Zeigers oder einer Referenz auf ein Objekt im Heap wird nur die (kleine) Referenz übergeben.
Scott Whitlock

1
Ich dachte, 3 wäre 'nein', denn wenn das Objekt mit Müll gesammelt wird, gibt es keine Referenz im Stapel, die darauf verweist.
Luciano

@ Luciano - Ich verstehe deinen Standpunkt. Frage 3 lese ich anders. Das "zur gleichen Zeit" oder "zu dieser Zeit" ist implizit. :: achselzucken ::
pdr

3
  1. Auf dem Heap, es sei denn, Java weist die Klasseninstanz auf dem Stack als Optimierung zu, nachdem mittels Escape-Analyse nachgewiesen wurde, dass dies keine Auswirkungen auf die Semantik hat. Dies ist jedoch ein Implementierungsdetail, sodass für alle praktischen Zwecke mit Ausnahme der Mikrooptimierung die Antwort "auf dem Haufen" lautet.

  2. Der Stapelspeicher muss in der Reihenfolge des letzten Vorgangs zugewiesen und freigegeben werden. Der Heapspeicher kann in beliebiger Reihenfolge zugewiesen und freigegeben werden.

  3. Wenn es sich bei dem Objekt um eine Garbage-Collection handelt, gibt es keine Referenzen mehr, die vom Stapel darauf verweisen. Wäre dies der Fall, würden sie das Objekt am Leben erhalten. Stapelprimitive werden überhaupt nicht mit Müll gesammelt, da sie automatisch zerstört werden, wenn die Funktion zurückkehrt.


3

Im Stapelspeicher werden lokale Variablen und Funktionsaufrufe gespeichert.

Während Heap-Speicher verwendet wird, um Objekte in Java zu speichern. Egal, wo das Objekt im Code erstellt wird.

Wo wird die primitive ain class Agespeichert werden?

In diesem Fall ist das Grundelement a einem Objekt der Klasse A zugeordnet. Es wird also im Heapspeicher erstellt.

Warum gibt es überhaupt Heapspeicher? Warum können wir nicht alles auf dem Stapel speichern?

  • Auf dem Stapel erstellte Variablen werden ungültig und automatisch zerstört.
  • Das Zuweisen von Stacks ist im Vergleich zu Variablen auf dem Heap wesentlich schneller.
  • Variablen auf dem Heap müssen vom Garbage Collector zerstört werden.
  • Im Vergleich zu Variablen auf dem Stapel langsamer zuzuweisen.
  • Sie würden den Stapel verwenden, wenn Sie genau wissen, wie viele Daten Sie vor der Kompilierung zuordnen müssen und diese nicht zu groß sind. (Primitive lokale Variablen werden im Stapel gespeichert.)
  • Sie würden den Heap verwenden, wenn Sie nicht genau wissen, wie viele Daten Sie zur Laufzeit benötigen oder wenn Sie viele Daten zuordnen müssen.

Wenn das Objekt Müll sammelt, wird der dem Objekt zugeordnete Stapel zerstört?

Der Garbage Collector arbeitet im Bereich des Heap-Speichers, sodass Objekte, die keine Referenzkette haben, vom Stammverzeichnis gelöscht werden.


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.