Heim  >  Artikel  >  Java  >  Die Lebensgeschichte von Java-Programmen

Die Lebensgeschichte von Java-Programmen

怪我咯
怪我咯Original
2017-04-05 16:45:031277Durchsuche

Als Programmierer schreiben wir jeden Tag Code, aber verstehen Sie wirklich seinen Lebenszyklus? Lassen Sie uns heute kurz über die Lebensgeschichte eines Java-Codes sprechen. Von der Geburt bis zum Ende des Spiels kann er grob in die folgenden Schritte unterteilt werden: Kompilierung, Laden der Klasse, Ausführen und GC.

Kompilierung

Der Kompilierungszeitraum der Java-Sprache ist tatsächlich ein „unsicherer“ Prozess, da es sich möglicherweise um ein Front-End handelt Compiler Der Prozess der Konvertierung von .java-Dateien in .class-Dateien kann sich auch auf den Prozess der Konvertierung von Bytecode in Maschinencode durch den Back-End-Laufzeit-Compiler (JIT-Compiler) beziehen statischer Fortschritt Der Compiler (AOT-Compiler) kompiliert die Datei .java direkt in lokalen Maschinencode. Aber hier sprechen wir über die erste Kategorie. Es entspricht auch unserem öffentlichen Verständnis von Zusammenstellung. Welche Prozesse durchlief die Zusammenstellung in diesem Zeitraum?

Lexikalische und syntaktische Analyse

Bei der lexikalischen Analyse geht es darum, den Zeichenstrom des Quellcodes in einen Token-Satz umzuwandeln, während bei der syntaktischen Analyse der Prozess der abstrakten Konstruktion eines Syntaxbaums (ATS) basierend auf Die Token-Sequenz ist eine Baumdarstellung, die zur Beschreibung der Syntaxstruktur des Programmcodes verwendet wird, z. B. Pakete, Typen, Modifikatoren, Operatoren. Schnittstelle, Rückgabewert und sogar CodeKommentare können eine Syntaxstruktur sein.

Füllen der Symboltabelle

Nach Abschluss der Syntax- und lexikalischen Analyse erfolgt im nächsten Schritt das Füllen der Symboltabelle. Die in der Symboltabelle registrierten Informationen werden in verschiedenen Phasen verwendet Zusammenstellung. Lassen Sie uns hier das Konzept der Symboltabelle erweitern. Was ist eine Symboltabelle? Es handelt sich um eine Tabelle, die aus einer Reihe von Symboladressen und Symbolinformationen besteht. Die einfachste Form kann als K-V-Wertepaar einer Hash-Tabelle verstanden werden. Warum werden Symboltabellen verwendet? Eine der frühesten Anwendungen von Symboltabellen bestand darin, Informationen über Programmcode zu organisieren. Anfangs waren Computerprogramme nur einfache Zahlenfolgen, aber Programmierer entdeckten bald, dass es viel bequemer war, Symbole zur Darstellung von Operationen und Speicheradressen zu verwenden (Variablennamen). Für die Zuordnung von Namen und Nummern ist eine Symboltabelle erforderlich. Wenn Programme wachsen, wird die Leistung von Symboltabellenoperationen allmählich zu einem Engpass für die Effizienz der Programmentwicklung. Aus diesem Grund wurden viele Datenstrukturen und Algorithmen entwickelt, um die Effizienz von Sequenznummerntabellen zu verbessern. Was sind die sogenannten Datenstrukturen und Algorithmen? Im Allgemeinen: sequentielle Suche in ungeordneten verknüpften Listen, binäre Suche in geordneten Arrays, binäre Suchbäume, ausgeglichene Suchbäume (hier kommen wir hauptsächlich mit Rot-Schwarz-Bäumen in Kontakt), Hash-Tabellen (Hash basierend auf der Zipper-Methode), Listen, Hash-Tabellen basierend auf linearer Antastung). Wie java.util.TreeMap und java.util.HashMap in Java werden sie basierend auf den Symboltabellen von Rot-Schwarz-Bäumen bzw. Zipper-Hash-Tabellen implementiert. Das Konzept der hier erwähnten Symboltabelle wird nicht näher erläutert. Interessierte können entsprechende Informationen finden.

Semantische Analyse

Nach den beiden vorherigen Schritten haben wir die abstrakte Syntaxbaumdarstellung des Programmcodes erhalten. Der Syntaxbaum kann eine korrekte Quellcodeabstraktion darstellen, es gibt jedoch keine Garantie dafür Das Quellprogramm ist logisch. Hier kommt die semantische Analyse ins Spiel. Ihre Hauptaufgabe besteht darin, die Kontextsensitivität des strukturell korrekten Quellprogramms zu überprüfen. Annotationsprüfung, Daten- und Kontrollflussanalyse und Dekodierung von Syntaxzucker sind mehrere Schritte in der Phase der semantischen Analyse. Hier werden wir das Konzept von Syntaxzucker im Detail diskutieren. Syntaktischer Zucker bezieht sich auf eine bestimmte Syntax, die einer Computersprache hinzugefügt wird. Diese Syntax hat keinen Einfluss auf die Funktionalität der Sprache, ist aber für Programmierer bequemer zu verwenden. Die am häufigsten verwendeten syntaktischen Zucker in Java sind Generika, Parameter variabler Länge, Self-Boxing/Unboxing und TraversalLoop. Die JVM unterstützt diese Syntaxen zur Laufzeit nicht und sie kehren während der Kompilierung zu einfachen Grundlagen zurück In dieser Phase der grammatikalischen Struktur wird der syntaktische Zucker gelöst. Um ein Beispiel für generisches Löschen zu geben: List werden nach der Kompilierung generisch gelöscht und werden zum gleichen nativen Typ List.

 Bytecode-Generierung

Die Bytecode-Generierung ist die letzte Phase des Javac-Kompilierungsprozesses. In dieser Phase werden die in den vorherigen Schritten generierten Informationen in Bytecode umgewandelt und auf die Festplatte geschrieben Es wurden nur geringe Code-Hinzufügungs- und Konvertierungsarbeiten durchgeführt. Instanzkonstruktor ()-Methode und Klassenkonstruktor ()-Methode (der Instanzkonstruktor bezieht sich hier nicht auf den Standard--Konstruktor ). Wenn der Benutzercode keinen Konstruktor bereitstellt, dann Der Compiler fügt einen Standardkonstruktor ohne Parameter und mit der gleichen Zugänglichkeit wie die aktuelle Klasse hinzu. Diese Arbeit wurde während der Füllphase der Symboltabelle abgeschlossen, und die Methode des Klassenkonstruktors () verweist darauf, dass der Compiler automatisch die Klasse All sammelt Klassenvariablenzuweisungsaktionen und Anweisungen in statischen Anweisungsblöcken werden in dieser Phase in den Syntaxbaum eingefügt. An diesem Punkt endet der gesamte Kompilierungsprozess.

Laden von Klassen

Kompilierung Nach dem Kompilieren des Programms in Bytecode besteht der nächste Schritt darin, Klassen in den Speicher zu laden.

Der Klassenladevorgang wird im Methodenbereich des Speichers der virtuellen Maschine ausgeführt, der den Speicher der virtuellen Maschine betrifft. Daher stellen wir hier zunächst kurz das Konzept der Programmverteilung im Speicherbereich vor. Der Speicherbereich der virtuellen Maschine ist unterteilt in: Programmzähler, Stapel, lokaler Methodenstapel, Heap, Methodenbereich (einige Bereiche sind Laufzeitkonstantenpools) und Direktspeicher.

Programmzähler

Der Programmzähler ist ein kleiner Speicherplatz. Er kann als Zeilennummernanzeige des vom aktuellen Thread ausgeführten Bytecodes betrachtet werden. Im JVM-Konzept Modell funktioniert der Bytecode-Interpreter, indem er den Wert dieses Zählers ändert, um den nächsten Bytecode-Befehl auszuwählen, der ausgeführt werden muss.

Stapel

Der Stapel wird zum Speichern lokaler Variablentabellen, Operandenstapel, dynamischer Links, Methodenexits und anderer Informationen verwendet. Die lokale Variablentabelle speichert verschiedene grundlegende Datentypen und ObjekteReferenzen, die während der Kompilierung eingeschränkt werden. Wie der Programmzähler ist er Thread-privat.

Lokaler Methodenstapel

Der lokale Methodenstapel ähnelt dem oben vorgestellten virtuellen Maschinenstapel. Der einzige Unterschied zwischen ihnen besteht darin, dass der virtuelle Maschinenstapel der virtuellen Maschine zur Ausführung von Java-Methoden (Bytecode) dient ), und der lokale Methodenstapel bedient die von der virtuellen Maschine verwendeten nativen Methoden, und einige virtuelle Maschinen kombinieren sogar beide zu einer.

Heap

Der Heap ist der größte Speicherbereich, der von der JVM verwaltet wird. Es handelt sich um einen von allen Threads gemeinsam genutzten Bereich. Sein einziger Zweck besteht darin, Objektinstanzen hier zu speichern (wie spezielle Klassenobjekte wird Speicher im Methodenbereich zugewiesen). Dieser Ort ist auch der Hauptbereich für die Speicherbereinigungsverwaltung. Aus Sicht des Speicherrecyclings verwenden Garbage Collectors jetzt Generationserfassungsalgorithmen (wird später ausführlich vorgestellt), sodass der Java-Heap weiter unterteilt werden kann: die neue Generation und die alte Generation und die neue Generation. Die Generation ist weiter unterteilt in: Eden-Raum, From Survivor Space und To Survivor Space. Aus Effizienzgründen kann der Heap auch in mehrere Thread-Private-Allokationspuffer (TLAB) unterteilt werden. Unabhängig davon, wie es aufgeteilt ist, hat es nichts mit dem Speicherinhalt zu tun. Unabhängig davon, in welchem ​​Bereich Objektinstanzen gespeichert werden, besteht der Zweck ihrer Existenz lediglich darin, Speicher besser zu recyceln und zuzuweisen.

Methodenbereich

Der Methodenbereich ist wie der Heap ein von Threads gemeinsam genutzter Speicherbereich. Er wird zum Speichern von Klasseninformationen, Konstanten, statischen Variablen und Just-in-Time-Compilern verwendet Kompilierung, die von der virtuellen Maschine geladen wurde, und andere Daten. Der Laufzeitkonstantenpool ist Teil des Methodenbereichs. Er wird hauptsächlich zum Speichern verschiedener Literale und Symbolreferenzen verwendet, die zur Kompilierungszeit deklariert werden.

Direkter Speicher

Direkter Speicher ist nicht Teil des Laufzeitdatenbereichs der virtuellen Maschine. Es handelt sich auch um einen Speicherbereich, der nicht in der Java-Spezifikation definiert ist. Sie können ihn einfach als Off-Heap-Speicher verstehen Die Speicherzuteilung wird nicht durch die Größe des Java-Heapspeichers beeinflusst, sondern durch die gesamte Speichergröße.

Nachdem wir über das Konzept des Speicherbereichs einer virtuellen Maschine gesprochen haben, kehren wir zum Thema zurück: Was ist der Klassenladeprozess? Fünf Schritte: Laden, Verifizieren, Vorbereiten, Parsen und Initialisieren. Laden, Überprüfen, Vorbereiten und Initialisieren werden nacheinander ausgeführt, das Parsen ist jedoch nicht unbedingt der Fall. Es kann nach der Initialisierung ausgeführt werden.

Laden

Während der Ladephase muss die JVM drei Schritte ausführen: Zuerst den binären Bytestrom abrufen, der diese Klasse über den vollständig qualifizierten Namen der Klasse definiert, und dann konvertieren Hierdurch dargestellter Bytestrom Die statische Speicherstruktur wird in die Laufzeitdatenstruktur des Methodenbereichs konvertiert und schließlich im Speicher ein java.lang.Class-Objekt generiert, das diese Klasse darstellt und als verschiedene Dateneinträge für diese Klasse dient Methodenbereich. Im ersten Schritt zum Abrufen des binären Bytestroms wird nicht klar angegeben, dass er aus einer *.class-Datei bezogen werden soll. Die Flexibilität der Vorschriften ermöglicht es uns, ihn aus der ZIP-Datei (die die Grundlage für JAR, EAR/ bereitstellt) zu erhalten. WAR-Formate) packen und aus dem Netzwerk abrufen (Applet), zur Laufzeit berechnen und generieren (dynamischer Proxy), andere Dateien generieren (Klassenklasse wird durch JSP-Datei generiert) und aus der Datenbank abrufen.

Verifizierung

Bei der Verifizierung geht es, wie der Name schon sagt, eigentlich darum, sicherzustellen, dass die im Klassendatei-Bytestrom enthaltenen Informationen den Anforderungen der JVM entsprechen, da die Quelle der Klassendatei nicht unbedingt aus generiert wird Der Compiler kann auch mit dem Hexadezimal- Editor generiert werden, der Klassendateien direkt schreibt. Der Überprüfungsprozess umfasst die Überprüfung des Dateiformats, der Metadaten und der Bytecode-Überprüfung. Die spezifischen Sicherheitsüberprüfungsmethoden werden hier nicht näher erläutert.

Vorbereitung

Die Vorbereitungsphase ist die Phase, in der Speicher für Klassenvariablen formal zugewiesen und Anfangswerte festgelegt werden. Der von diesen Variablen verwendete Speicher wird im Methodenbereich zugewiesen.

Analyse

Die Analysephase ist der Prozess, in dem die JVM die Symbolreferenz im Konstantenpool durch eine direkte Referenz (einen Zeiger auf das Ziel, einen relativen Offset oder ein Handle) ersetzt. Die Kompilierungsauffüllung, über die wir zuvor gesprochen haben, spiegelt hier den Wert der Symboltabelle wider. Der Parsing-Prozess ist nichts anderes als das Parsen von Klassen oder Schnittstellen, Feldern und Schnittstellenmethoden.

Initialisierung

Die Klasseninitialisierungsphase ist der letzte Schritt im Klassenladeprozess. In der Vorbereitungsphase wurde den Variablen ein Anfangswert zugewiesen, der in diesem Schritt übernommen wird Initialisieren Sie Klassenvariablen und andere Ressourcen gemäß den vom Programmierer angepassten Anforderungen. In dieser Phase wird die Methode () ausgeführt, die im vorherigen Prozess zur Generierung des kompilierten Bytecodes erwähnt wurde. Die virtuelle Maschine stellt außerdem sicher, dass diese Methode beim gleichzeitigen Aufruf in einer Multithread-Umgebung korrekt gesperrt und synchronisiert wird, sodass nur ein Thread diese Methode ausführt, während andere Threads blockieren und warten. Der Autor hat zuvor einen Artikel geschrieben: „Von a Einfaches Java im Singleton-Beispiel: Apropos Parallelität: Die Thread-sichere Schreibmethode von Singleton basierend auf der Klasseninitialisierung hängt damit zusammen. Wenn Sie interessiert sind, können Sie sie kombinieren und einen Blick darauf werfen. An dieser Stelle geht es auch um einen weiteren Wissenspunkt, der uns mehr Sorgen bereitet. Wann löst Java den Initialisierungsvorgang einer Klasse aus?

  • Wenn Sie auf die vier Bytecode-Anweisungen new, getstatic, putstatic oder invokestatic stoßen und die Klasse nicht initialisiert wurde, muss ihre Initialisierung ausgelöst werden Ein einfaches Verständnis ist, wenn Sie ein neues Objekt erstellen, wenn Sie ein statisches Feld einer Klasse lesen oder festlegen oder wenn Sie eine statische Methode einer Klasse aufrufen.

  • Wenn Sie die Methode des java.lang.reflect-Pakets verwenden, um einen reflektierenden Aufruf einer Klasse durchzuführen, muss die Initialisierung ausgelöst werden, wenn die Klasse nicht initialisiert ist.
    Wenn Sie eine Klasse initialisieren und feststellen, dass ihre übergeordnete Klasse nicht initialisiert wurde, wird zuerst der Initialisierungsvorgang ihrer übergeordneten Klasse ausgelöst.

  • Wenn die virtuelle Maschine startet, muss der Benutzer eine auszuführende Hauptklasse angeben (die Klasse, in der sich die Hauptmethode befindet), und die virtuelle Maschine initialisiert diese Hauptklasse zuerst .

  • Bei Verwendung der dynamischen Sprachunterstützung über JDK1.7, wenn das endgültige Parsing-Ergebnis einer java.lang.invoke.MethodHandle-Instanz das Methodenhandle von REF_getStatic, REF_putStatic, REF_invokeStatic usw. ist Wenn die dem Methodenhandle entsprechende Klasse nicht initialisiert wurde, wird der Initialisierungsvorgang ausgelöst.

Ausführen

Nach den oben genannten zwei Phasen beginnt das Programm normal zu laufen. Wir alle wissen, dass der Programmausführungsprozess die Berechnungsoperationen verschiedener Anweisungen umfasst das Programm? Wie sieht es mit der Ausführung aus? An dieser Stelle werden der am Anfang des Artikels erwähnte Back-End-Compiler (JIT-Just-in-Time-Compiler) + Interpreter (die virtuelle HotSpot-Maschine verwendet standardmäßig einen Interpreter und einen Compiler) und Bytecode verwendet Ausführung Die Engine ist für die Aufgaben verschiedener Programmberechnungsvorgänge verantwortlich. Bei der Ausführung von Java-Code gibt es möglicherweise zwei Optionen: interpretierte Ausführung (durch einen Interpreter ausgeführt) und kompilierte Ausführung (lokaler Code, der durch einen Just-in-Time-Compiler generiert wird). Oder vielleicht beides. Der Stapelrahmen ist eine Datenstruktur, die zur Unterstützung des Methodenaufrufs und der Ausführung virtueller Maschinen verwendet wird. Die spezifischen Berechnungsideen verschiedener Anweisungen zum Schieben und Knallen des Stapels umfassen einen klassischen Algorithmus – Dijkstra-Algorithmus. Wenn Sie interessiert sind, wie er ausgeführt wird. Überprüfen Sie die Informationen selbst. Dieser Ort geht nicht zu tief. Fragen der Laufzeitoptimierung sind in dieser Phase gleichermaßen wichtig, und das JVM-Designteam hat sich in dieser Phase auf die Leistungsoptimierung konzentriert, sodass auch Klassendateien, die nicht von Javac generiert wurden, die Vorteile der Compileroptimierung nutzen können. Was sind die Optimierungstechniken? Es gibt viele. Hier sind einige repräsentative Optimierungstechniken: Eliminierung gemeinsamer Unterausdrücke, Eliminierung von Array-Grenzen, Methoden-Inlining, Escape-Analyse usw.

GC

Schließlich heißt es, dass das Programm in die Todesphase eintritt. Wie bestimmt die JVM Programmpillen? Dieser Ort verwendet tatsächlich einen Erreichbarkeitsanalysealgorithmus. Die Grundidee dieses Algorithmus besteht darin, eine Reihe von Objekten namens „GC Roots“ als Ausgangspunkt zu verwenden und von diesem Knoten aus nach unten zu suchen Als Referenzkette bezeichnet. Wenn es keine Referenzkette gibt, die ein Objekt mit GC Roots verbindet (in der Graphentheorie bedeutet dies, dass das Objekt von GC Roots aus nicht erreichbar ist), beweist dies, dass das Objekt nicht verfügbar ist, und es wird festgestellt, dass dies der Fall ist ein wiederverwertbares Objekt. Wann lösen wir die Garbage Collection aus, wenn wir die zu recycelnden Objekte bereits kennen? Sicherheitspunkte sind Orte, an denen das Programm vorübergehend ausgeführt wird, um GC durchzuführen. Daraus können wir leicht erkennen, dass die GC-Pausenzeit der Kern der Speicherbereinigung ist. Alle Garbage-Collection-Algorithmen und abgeleiteten Garbage-Collectors konzentrieren sich auf die Minimierung der GC-Pausenzeiten. Jetzt kann der neueste G1-Garbage-Collector ein vorhersehbares Pausenzeitmodell erstellen und planen, vollständige GC-Pausen im gesamten Java-Heap zu vermeiden. Als wir zuvor das Konzept der Speicherbereichsverteilung eingeführt haben, haben wir über die neue Generation und die alte Generation gesprochen. Verschiedene Garbage Collectors können auf die neue Generation oder die alte Generation einwirken, und es gibt nicht einmal ein Generierungskonzept (wie den G1-Collector). ). Im Folgenden finden Sie jedoch eine detaillierte Einführung in den Garbage Collection-Algorithmus und den entsprechenden Garbage Collector.

Mark-Clear-Algorithmus

Der grundlegendste Sammelalgorithmus ist unterteilt In zwei Arten unterteilt: Markieren und Löschen. Phase: Markieren Sie zunächst alle zu recycelnden Objekte. Nach Abschluss der Markierung werden alle markierten Objekte einheitlich recycelt. Sein größtes Manko besteht darin, dass es nicht effizient ist und eine große Anzahl diskontinuierlicher Speicherfragmente generiert. Dies führt zu Problemen, wenn das Programm während der Ausführung große Objekte zuweist. Es kann jedoch sein, dass es nicht genügend kontinuierlichen Speicher findet um eine GC-Operation auszulösen. Der entsprechende Garbage Collector ist hier der CMS Collector.

Kopieralgorithmus

Der Kopieralgorithmus wurde entwickelt, um Effizienzprobleme zu lösen. Er kann die verfügbare Speicherkapazität in zwei gleich große Blöcke aufteilen und jeweils nur einen davon verwenden Wenn der Speicher aufgebraucht ist, kopieren Sie die verbleibenden Objekte in einen anderen Block und bereinigen Sie dann sofort den verwendeten Speicherplatz. Auf diese Weise wird die GC jedes Mal für die gesamte Halbfläche durchgeführt und es treten keine Probleme wie Speicherfragmentierung auf. Die meisten heutigen kommerziellen virtuellen Maschinen verwenden diesen Algorithmus, um die neue Generation zu recyceln. Darüber hinaus beträgt das Speicherteilungsverhältnis nicht 1:1. Beispielsweise beträgt das Standardverhältnis von Eden (ein Eden-Bereich) und Survivor (zwei Survivor-Bereiche). Jedes Mal, wenn Eden und einer der Surviovr-Bereiche verwendet werden, beträgt der verfügbare Speicherplatz in der neuen Generation 90 % der gesamten neuen Generation. Kopieren Sie beim Recycling die überlebenden Objekte in Eden und einem der Bereinigen Sie schließlich Eden und den gerade genutzten Survivor-Speicherplatz. Aufmerksame Leser können hier herausfinden, was passiert, wenn der ungenutzte Survivor-Speicherplatz während des Kopiervorgangs nicht ausreicht. Zu diesem Zeitpunkt müssen Sie sich bei der Zuweisungsgarantie auf die alte Generation verlassen. Wenn die Garantie erfolgreich ist, werden Eden und eines der überlebenden Objekte im Survivor auf die alte Generation verschoben in der alten Generation ausgelöst werden. Um diesen Punkt zu erweitern, wird die Garbage Collection der neuen Generation als Minor GC bezeichnet. Da die meisten Java-Objekte geboren und sterben, ist Minor GC sehr häufig und die Wiederherstellungsgeschwindigkeit ist im Allgemeinen hoch. Die Garbage Collection der alten Generation wird als Major GC/Full GC bezeichnet. Die Geschwindigkeit von Major GC ist im Allgemeinen viel langsamer als die von Minor GC. Aus dem vorherigen Analyseprozess können wir leicht schließen, dass das Auftreten von Major GC häufig von einem Minor GC begleitet wird Unser GC besteht eigentlich darin, die Geschwindigkeit des GC anzupassen. Es ist am besten, die Häufigkeit des Major GC so weit wie möglich zu kontrollieren und zu reduzieren. Die entsprechenden Garbage Collectors sind hier der Serial Collector, der ParNew Collector (eine Multithread-Version des Serial Collectors, die mit dem später erwähnten Collector CMS der alten Generation arbeiten kann) und der Parallel Scavenge Collector.

 Mark-Collation-Algorithmus

Dieser Algorithmus wird auf den Garbage-Collection-Algorithmus der alten Generation angewendet, da die alte Generation nicht so häufig recycelt wird wie der Kopieralgorithmus und außerdem Platz verschwendet wird. Der Mark-Organize-Prozess ähnelt Mark-Clear, mit der Ausnahme, dass die folgenden Schritte nicht darin bestehen, die wiederverwertbaren Objekte direkt zu löschen, sondern alle überlebenden Objekte an ein Ende zu verschieben und dann den Speicher außerhalb der Endgrenze direkt zu bereinigen. Die entsprechenden Garbage Collectors sind hier Serial Old Collector und Parallel Old Collector.

Generationssammlungsalgorithmus

Aktuelle kommerzielle virtuelle Maschinen verwenden alle diesen Algorithmus. Seine Idee besteht darin, den Heap-Speicherbereich in Generationen aufzuteilen. Die neue Generation und die alte Generation sind unterschiedlich. Die Regionen verwenden unterschiedliche Garbage-Collection-Algorithmen. Die junge Generation verwendet den Kopieralgorithmus und die alte Generation verwendet den Mark-Collation- oder Mark-Sweep-Algorithmus.

Rezension

Nachdem Sie so viel gesprochen haben, haben Sie vielleicht eine Vorstellung von der Lebensgeschichte von Java-Code, oder Sie verstehen ihn nicht sehr gut. Hier geben wir ein Beispiel, um das Ganze zu überprüfen Was werden wir erleben, wenn wir ein neues Objekt erstellen? In Kombination mit dem zuvor Gesagten prüft die JVM, wenn sie auf eine neue Anweisung trifft, zunächst, ob der gesamte Anweisungsparameter eine Symbolreferenz einer Klasse im Konstantenpool im Methodenbereich finden kann, und prüft, ob die Klasse durch das gesamte Symbol dargestellt wird Die Referenz wurde geladen, analysiert und initialisiert. Wenn nicht, muss zuerst der entsprechende Klassenladevorgang ausgeführt werden. Nachdem die Klassenladeprüfung bestanden wurde, weist die JVM als nächstes Speicher für das neue Objekt zu. Die Zuweisungsgröße kann ermittelt werden, nachdem das Laden der Klasse abgeschlossen ist Wird verwendet, um die Objektgröße zu verschieben. Diese Zuordnungsmethode wird als „Zeigerkollision“ bezeichnet. Wenn sie verstreut ist, verwaltet die JVM eine Liste, um den verfügbaren Speicher aufzuzeichnen, und aktualisiert die Listendatensätze „Freie Liste“: Welche Methode verwendet wird, hängt davon ab, welcher Garbage Collector für den zuvor erwähnten Heap verwendet wird. Nach der Aufteilung des Objektspeichers führt die virtuelle Maschine die erforderlichen Initialisierungsvorgänge durch. Anschließend müssen die erforderlichen Einstellungen für das Objekt vorgenommen werden. Diese Informationen werden im Objektheader festgelegt (Klassenmetadateninformationen, Objekt-Hashcode, Alter der Objekt-GC-Generierung usw.). .) Nach Abschluss dieser Aufgaben wird ein neues Objekt generiert. Der nächste Schritt besteht darin, die vom Programmierer geplante Zuweisungsoperation für das Objektfeld auszuführen. und schließlich den Stapel festlegen. Die Referenz zeigt auf die Speicheradresse, an der sich das Objekt im Heap befindet (direkte Referenz). Zu diesem Zeitpunkt wurde ein wirklich verwendbares Objekt für die verschiedenen nachfolgenden Operationen am Objekt und seinen endgültigen Tod generiert , es ist die zuvor erwähnte Bytecode-Ausführungs-Engine, ich glaube, sie ist nicht mehr jedem unbekannt.

 

Das obige ist der detaillierte Inhalt vonDie Lebensgeschichte von Java-Programmen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn