Ich versuche, den tatsächlichen Prozess hinter der Objekterstellung in Java zu verstehen - und ich nehme an, andere Programmiersprachen.
Wäre es falsch anzunehmen, dass die Objektinitialisierung in Java dieselbe ist wie bei Verwendung von malloc für eine Struktur in C?
Beispiel:
Foo f = new Foo(10);
typedef struct foo Foo;
Foo *f = malloc(sizeof(Foo));
Sollen sich deshalb Objekte eher auf dem Haufen als auf dem Stapel befinden? Weil sie im Wesentlichen nur Zeiger auf Daten sind?
scalar-replacement
in einfache Felder "zerlegt" (aufgerufen ) werden, die nur auf dem Stapel leben. aber das ist etwas, wasJIT
nicht tutjavac
.Antworten:
Weist in C
malloc()
einen Speicherbereich im Heap zu und gibt einen Zeiger darauf zurück. Das ist alles was du bekommst. Der Speicher ist nicht initialisiert und Sie können nicht garantieren, dass alles Nullen oder etwas anderes ist.In Java führt das Aufrufen
new
eine Heap-basierte Zuweisung ausmalloc()
, aber Sie erhalten auch eine Menge zusätzlichen Komfort (oder Overhead, wenn Sie dies bevorzugen). Beispielsweise müssen Sie die Anzahl der zuzuweisenden Bytes nicht explizit angeben. Der Compiler ermittelt dies für Sie anhand des Objekttyps, den Sie zuweisen möchten. Darüber hinaus werden Objektkonstruktoren aufgerufen (an die Sie Argumente übergeben können, wenn Sie steuern möchten, wie die Initialisierung erfolgt). Bei dernew
Rückkehr erhalten Sie garantiert ein Objekt, das initialisiert wurde.Aber ja, am Ende des Aufrufs sind sowohl das Ergebnis
malloc()
alsnew
auch Zeiger auf einen Teil der Heap-basierten Daten.Der zweite Teil Ihrer Frage fragt nach den Unterschieden zwischen einem Stapel und einem Haufen. Weitaus umfassendere Antworten finden Sie in einem Kurs über das Compiler-Design (oder in einem Buch darüber). Ein Kurs über Betriebssysteme wäre ebenfalls hilfreich. Es gibt auch zahlreiche Fragen und Antworten zu SO über die Stapel und Haufen.
Trotzdem gebe ich einen allgemeinen Überblick, von dem ich hoffe, dass er nicht zu ausführlich ist, und möchte die Unterschiede auf einem ziemlich hohen Niveau erklären.
Grundsätzlich liegt der Hauptgrund für zwei Speicherverwaltungssysteme, dh einen Heap und einen Stack, in der Effizienz . Ein sekundärer Grund ist, dass jeder bei bestimmten Arten von Problemen besser ist als der andere.
Stapel sind für mich als Konzept etwas leichter zu verstehen, daher beginne ich mit Stapeln. Betrachten wir diese Funktion in C ...
Das obige scheint ziemlich einfach zu sein. Wir definieren eine Funktion mit dem Namen
add()
und übergeben die linken und rechten Addends. Die Funktion fügt sie hinzu und gibt ein Ergebnis zurück. Bitte ignorieren Sie alle Randfälle wie Überläufe, die auftreten können. An dieser Stelle ist dies für die Diskussion nicht relevant.Der
add()
Zweck der Funktion scheint ziemlich einfach zu sein, aber was können wir über ihren Lebenszyklus sagen? Besonders die Speicherauslastung benötigt?Am wichtigsten ist, dass der Compiler a priori (dh zur Kompilierungszeit) weiß , wie groß die Datentypen sind und wie viele verwendet werden. Die Argumente
lhs
undrhs
sind jeweilssizeof(int)
4 Bytes. Die Variableresult
ist auchsizeof(int)
. Der Compiler kann feststellen, dass dieadd()
Funktion4 bytes * 3 ints
insgesamt 12 Byte Speicher verwendet.Wenn die
add()
Funktion aufgerufen wird, enthält ein Hardware-Register, das als Stapelzeiger bezeichnet wird, eine Adresse, die auf die Oberseite des Stapels zeigt. Um den Speicher zuzuweisen, den dieadd()
Funktion ausführen muss, muss der Funktionseintragscode lediglich eine einzelne Assembler-Anweisung ausgeben, um den Stapelzeigerregisterwert um 12 zu verringern. Auf diese Weise wird Speicher auf dem Stapel für drei Personen erstelltints
, jeweils fürlhs
,rhs
, undresult
. Das Erhalten des Speicherplatzes, den Sie durch Ausführen eines einzelnen Befehls benötigen, ist ein enormer Geschwindigkeitsgewinn, da einzelne Befehle in der Regel in einem Takt ausgeführt werden (1 Milliardstel Sekunde einer 1-GHz-CPU).Aus Sicht des Compilers kann auch eine Zuordnung zu den Variablen erstellt werden, die der Indizierung eines Arrays sehr ähnlich sieht:
Auch dies alles ist sehr schnell.
Wenn die
add()
Funktion beendet wird, muss sie bereinigt werden. Dies geschieht durch Subtrahieren von 12 Bytes vom Stapelzeigerregister. Es ähnelt einem Aufruf von, verwendetfree()
jedoch nur einen CPU-Befehl und nur einen Tick. Es ist sehr, sehr schnell.Betrachten Sie nun eine Heap-basierte Zuordnung. Dies kommt ins Spiel, wenn wir nicht a priori wissen, wie viel Speicher wir benötigen werden (dh wir werden erst zur Laufzeit davon erfahren).
Betrachten Sie diese Funktion:
Beachten Sie, dass die
addRandom()
Funktion zur Kompilierungszeit nicht weiß, wie hoch der Wert descount
Arguments sein wird. Aus diesem Grund ist es nicht sinnvoll zu versuchen, so zu definieren,array
wie wir es tun würden, wenn wir es auf den Stapel legen würden:Wenn
count
es riesig ist, kann es dazu führen, dass unser Stack zu groß wird und andere Programmsegmente überschreibt. Wenn dieser Stapelüberlauf auftritt , stürzt Ihr Programm ab (oder schlimmer).In Fällen, in denen wir nicht wissen, wie viel Speicher wir bis zur Laufzeit benötigen, verwenden wir
malloc()
. Dann können wir einfach nach der Anzahl der benötigten Bytes fragen, wenn wir sie benötigen, undmalloc()
prüfen, ob sie so viele Bytes verkaufen können. Wenn es geht, großartig, bekommen wir es zurück, wenn nicht, bekommen wir einen NULL-Zeiger, der uns sagt, dass der Aufrufmalloc()
fehlgeschlagen ist. Insbesondere stürzt das Programm jedoch nicht ab! Natürlich können Sie als Programmierer entscheiden, dass Ihr Programm nicht ausgeführt werden darf, wenn die Ressourcenzuweisung fehlschlägt, aber die vom Programmierer initiierte Beendigung unterscheidet sich von einem falschen Absturz.Jetzt müssen wir zurückkommen, um die Effizienz zu untersuchen. Der Stapelzuweiser ist superschnell - ein Befehl zum Zuweisen, ein Befehl zum Freigeben und wird vom Compiler ausgeführt. Denken Sie jedoch daran, dass der Stapel für Dinge wie lokale Variablen bekannter Größe gedacht ist, sodass er eher klein ist.
Der Heap-Allokator hingegen ist um mehrere Größenordnungen langsamer. Es muss in Tabellen nachgeschlagen werden, um festzustellen, ob genügend freier Speicher vorhanden ist, um die vom Benutzer gewünschte Speichermenge zu verkaufen. Diese Tabellen müssen nach dem Verkauf des Speichers aktualisiert werden, um sicherzustellen, dass niemand diesen Block verwenden kann (für diese Buchhaltung muss der Allokator möglicherweise zusätzlich zu dem, was er verkaufen möchte, Speicher für sich selbst reservieren). Der Allokator muss Sperrstrategien anwenden, um sicherzustellen, dass der Speicher threadsicher verkauft wird. Und wenn die Erinnerung endlich ist
free()
d, was zu unterschiedlichen Zeiten und normalerweise in keiner vorhersehbaren Reihenfolge geschieht, muss der Allokator zusammenhängende Blöcke finden und sie wieder zusammenfügen, um die Haufenfragmentierung zu reparieren. Wenn das so klingt, als würde es mehr als eine einzige CPU-Anweisung erfordern, um all das zu erreichen, haben Sie Recht! Es ist sehr kompliziert und es dauert eine Weile.Aber Haufen sind groß. Viel größer als Stapel. Wir können viel Speicher von ihnen erhalten und sie sind großartig, wenn wir zur Kompilierungszeit nicht wissen, wie viel Speicher wir benötigen. Wir tauschen also die Geschwindigkeit gegen ein verwaltetes Speichersystem aus, das uns höflich ablehnt, anstatt abzustürzen, wenn wir versuchen, etwas zu Großes zuzuweisen.
Ich hoffe, das hilft bei der Beantwortung einiger Ihrer Fragen. Bitte lassen Sie mich wissen, wenn Sie eine Erläuterung zu einem der oben genannten Punkte wünschen.
quelle
int
beträgt auf einer 64-Bit-Plattform nicht 8 Byte. Es ist immer noch 4. Gleichzeitig optimiert der Compiler sehr wahrscheinlich den drittenint
Teil des Stapels in das Rückgaberegister. Tatsächlich befinden sich die beiden Argumente wahrscheinlich auch in Registern auf jeder 64-Bit-Plattform.int
auf 64-Bit-Plattformen zu entfernen . Sie sind richtig, dassint
in Java 4 Bytes bleiben. Ich habe den Rest meiner Antwort jedoch hinterlassen, weil ich glaube, dass der Einstieg in die Compiler-Optimierung den Wagen vor das Pferd stellt. Ja, auch in diesen Punkten haben Sie Recht, aber in der Frage wird um Klärung von Stapeln und Haufen gebeten. RVO, Argumentation über Register, Code-Elision usw. überlasten die Grundkonzepte und behindern das Verständnis der Grundlagen.