Von monolithischen Strukturen bis zur Welt verteilter Systeme hat die Anwendungsentwicklung einen langen Weg zurückgelegt. Die massive Einführung von Cloud Computing und Microservice-Architektur hat die Herangehensweise an die Erstellung und Bereitstellung von Serveranwendungen erheblich verändert. Anstelle riesiger Anwendungsserver verfügen wir jetzt über unabhängige, individuell bereitgestellte Dienste, die sofort in Aktion treten
nach Bedarf.
Ein neuer Spieler im Block, der dieses reibungslose Funktionieren beeinträchtigen kann, könnte jedoch „Kaltstarts“ sein. Kaltstarts treten ein, wenn die erste Anfrage für einen frisch erzeugten Arbeiter verarbeitet wird. Diese Situation erfordert eine Initialisierung der Sprachlaufzeit und der Dienstkonfiguration, bevor die eigentliche Anfrage verarbeitet wird. Die mit Kaltstarts verbundene Unvorhersehbarkeit und langsamere Ausführung kann zu einem Verstoß gegen die Service Level Agreements eines Cloud-Dienstes führen. Wie kann man also dieser wachsenden Besorgnis begegnen?
Um die Ineffizienzen von Kaltstarts zu bekämpfen, wurde ein neuartiger Ansatz entwickelt, der Point-to-Analyse, Anwendungsinitialisierung zur Erstellungszeit, Heap-Snapshotting und vorzeitige (AOT)-Kompilierung umfasst. Diese Methode basiert auf der Annahme einer geschlossenen Welt und erfordert, dass alle Java-Klassen zum Zeitpunkt der Erstellung vorbestimmt und zugänglich sind. In dieser Phase ermittelt eine umfassende Points-to-Analyse alle erreichbaren Programmelemente (Klassen, Methoden, Felder), um sicherzustellen, dass nur wesentliche Java-Methoden kompiliert werden.
Der Initialisierungscode für die Anwendung kann während des Build-Prozesses und nicht zur Laufzeit ausgeführt werden. Dies ermöglicht die Vorabzuweisung von Java-Objekten und den Aufbau komplexer Datenstrukturen, die dann zur Laufzeit über einen „Image-Heap“ zur Verfügung gestellt werden. Dieser Image-Heap ist in die ausführbare Datei integriert und sorgt so für sofortige Verfügbarkeit beim Start der Anwendung. Der
Die iterative Ausführung der Point-to-Analyse und des Snapshots wird fortgesetzt, bis ein stabiler Zustand (Fixpunkt) erreicht ist, wodurch sowohl die Startzeit als auch der Ressourcenverbrauch optimiert werden.
Die Eingabe für unser System ist Java-Bytecode, der aus Sprachen wie Java, Scala oder Kotlin stammen könnte. Der Prozess behandelt die Anwendung, ihre Bibliotheken, das JDK und die VM-Komponenten einheitlich, um eine native ausführbare Datei zu erstellen, die für ein Betriebssystem und eine Architektur spezifisch ist – ein sogenanntes „natives Image“. Der Erstellungsprozess umfasst iterative Points-to-Analysen und Heap-Snapshots, bis ein fester Punkt erreicht ist, sodass die Anwendung über registrierte Rückrufe aktiv teilnehmen kann. Diese Schritte werden zusammenfassend als nativer Image-Erstellungsprozess bezeichnet (Abbildung 1)
Abbildung 1 – Native Image-Erstellungsprozess (Quelle: redhat.com)
Wir verwenden eine Point-to-Analyse, um die Erreichbarkeit von Klassen, Methoden und Feldern zur Laufzeit zu ermitteln. Die Punkt-zu-Analyse beginnt mit allen Einstiegspunkten, wie etwa der Hauptmethode der Anwendung, und durchläuft iterativ alle transitiv erreichbaren Methoden, bis ein fester Punkt erreicht wird (Abbildung 2).
Abbildung 2 – Points-to-Analyse
Unsere Point-to-Analyse nutzt das Frontend unseres Compilers, um Java-Bytecode in die Zwischendarstellung auf hoher Ebene des Compilers zu analysieren (IR). Anschließend wird die IR in einen Typ-Fluss-Graphen umgewandelt. In diesem Diagramm stellen Knoten Anweisungen dar, die auf Objekttypen wirken, während Kanten gerichtete Nutzungskanten zwischen Knoten bezeichnen, die von der Definition auf die Verwendung verweisen. Jeder Knoten verwaltet einen Typstatus, der aus einer Liste von Typen besteht, die den Knoten erreichen können, und Nullheitsinformationen. Typzustände breiten sich über die Nutzungskanten aus; Wenn sich der Typstatus eines Knotens ändert, wird diese Änderung an alle Verwendungen weitergegeben. Wichtig ist, dass Typzustände nur erweitert werden können. Neue Typen können zu einem Typstatus hinzugefügt werden, bestehende Typen werden jedoch niemals entfernt. Dieser Mechanismus stellt sicher, dass die
Die Analyse konvergiert letztendlich zu einem festen Punkt und führt zur Beendigung.
Die Points-to-Analyse steuert die Ausführung des Initialisierungscodes, wenn dieser einen lokalen Fixpunkt erreicht. Dieser Code hat seinen Ursprung in zwei separaten Quellen: Klasseninitialisierern und benutzerdefiniertem Code-Batch, der zur Build-Zeit über eine Feature-Schnittstelle ausgeführt wird:
Klasseninitialisierer: Jede Java-Klasse kann einen Klasseninitialisierer haben, der durch eine
Explizite Rückrufe: Entwickler können benutzerdefinierten Code über von unserem System bereitgestellte Hooks implementieren und vor, während oder nach den Analysephasen ausführen.
Hier sind die APIs, die für die Integration in unser System bereitgestellt werden.
boolean isReachable(Class> clazz); boolean isReachable(Field field); boolean isReachable(Executable method);
Weitere Informationen finden Sie im QueryReachabilityAccess
void registerReachabilityHandler(Consumercallback, Object... elements); void registerSubtypeReachabilityHandler(BiConsumer > callback, Class> baseClass); void registerMethodOverrideReachabilityHandler(BiConsumer callback, Executable baseMethod);
Weitere Informationen finden Sie im BeforeAnalysisAccess
Während dieser Phase kann die Anwendung benutzerdefinierten Code ausführen, z. B. die Objektzuweisung und die Initialisierung größerer Datenstrukturen. Wichtig ist, dass der Initialisierungscode auf den aktuellen Points-to-Analysestatus zugreifen kann und so Abfragen hinsichtlich der Erreichbarkeit von Typen, Methoden oder Feldern ermöglicht. Dies wird mithilfe der verschiedenen isReachable()-Methoden erreicht, die von WhileAnalysisAccess bereitgestellt werden. Mithilfe dieser Informationen kann die Anwendung Datenstrukturen erstellen, die für die erreichbaren Segmente der Anwendung optimiert sind.
Schließlich erstellt das Heap-Snapshotting einen Objektgraphen, indem es Stammzeigern wie statischen Feldern folgt, um eine umfassende Ansicht aller erreichbaren Objekte zu erstellen. Dieses Diagramm füllt dann die
des nativen Bildes aus.
Image-Heap, um sicherzustellen, dass der Anfangszustand der Anwendung beim Start effizient geladen wird.
Um den transitiven Abschluss erreichbarer Objekte zu generieren, durchläuft der Algorithmus Objektfelder und liest ihre Werte mithilfe von Reflektion. Es ist wichtig zu beachten, dass der Image Builder in der Java-Umgebung ausgeführt wird. Bei diesem Durchlauf werden nur Instanzfelder berücksichtigt, die von der Punkt-zu-Analyse als „gelesen“ markiert wurden. Wenn eine Klasse beispielsweise zwei Instanzfelder hat, eines jedoch nicht als gelesen markiert ist, wird das über das nicht markierte Feld erreichbare Objekt aus dem Bildheap ausgeschlossen.
Wenn ein Feldwert auftritt, dessen Klasse zuvor nicht durch die Points-to-Analyse identifiziert wurde, wird die Klasse als Feldtyp registriert. Diese Registrierung stellt sicher, dass in nachfolgenden Iterationen der Point-to-Analyse der neue Typ an alle Feldlesevorgänge und transitiven Verwendungen im Typflussdiagramm weitergegeben wird.
Das folgende Code-Snippet beschreibt den Kernalgorithmus für Heap-Snapshots:
Declare List worklist := [] Declare Set reachableObjects := [] Function BuildHeapSnapshot(PointsToState pointsToState) For Each field in pointsToState.getReachableStaticObjectFields() Call AddObjectToWorkList(field.readValue()) End For For Each method in pointsToState.getReachableMethods() For Each constant in method.embeddedConstants() Call AddObjectToWorkList(constant) End For End For While worklist.isNotEmpty Object current := Pop from worklist If current Object is an Array For Each value in current Call AddObjectToWorkList(value) Add current.getClass() to pointsToState.getObjectArrayTypes() End For Else For Each field in pointsToState.getReachableInstanceObjectFields(current.getClass()) Object value := field.read(current) Call AddObjectToWorkList(value) Add value.getClass() to pointsToState.getFieldValueTypes(field) End For End If End While Return reachableObjects End Function
Zusammenfassend lässt sich sagen, dass der Heap-Snapshotting-Algorithmus effizient einen Snapshot des Heaps erstellt, indem er erreichbare Objekte und ihre Felder systematisch durchläuft. Dadurch wird sichergestellt, dass nur relevante Objekte im Bildheap enthalten sind, wodurch die Leistung und der Speicherbedarf des nativen Bildes optimiert werden.
Zusammenfassend lässt sich sagen, dass der Prozess des Heap-Snapshottings eine entscheidende Rolle bei der Erstellung nativer Images spielt. Durch das systematische Durchlaufen erreichbarer Objekte und ihrer Felder erstellt der Heap-Snapshotting-Algorithmus einen Objektgraphen, der den transitiven Abschluss erreichbarer Objekte aus Stammzeigern wie statischen Feldern darstellt. Dieser Objektgraph wird dann als Image-Heap in das native Image eingebettet und dient als erster Heap beim Start des nativen Images.
Während des gesamten Prozesses verlässt sich der Algorithmus auf den Status der Points-to-Analyse, um zu bestimmen, welche Objekte und Felder für die Aufnahme in den Bildheap relevant sind. Objekte und Felder, die von der Punkt-zu-Analyse als „gelesen“ markiert wurden, werden berücksichtigt, während nicht markierte Entitäten ausgeschlossen werden. Wenn auf zuvor unbekannte Typen stößt, registriert der Algorithmus diese außerdem zur Weitergabe in nachfolgenden Iterationen der Points-to-Analyse.
Insgesamt optimiert das Heap-Snapshotting die Leistung und Speichernutzung nativer Bilder, indem es sicherstellt, dass nur notwendige Objekte im Bild-Heap enthalten sind. Dieser systematische Ansatz erhöht die Effizienz und Zuverlässigkeit der nativen Bildausführung.
Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.
Copyright© 2022 湘ICP备2022001581号-3