Start / Seminare / Quarkus für Spring-Boot-Teams
Modul
JVM, AOT und Native Image vergleichen
4 Kapitel in diesem Modul-Video · Laufzeit
Transkript
Der gesprochene Text dieses Moduls zum Mitlesen, Überfliegen und Durchsuchen. Ein Klick auf einen Zeitstempel springt an die Stelle im Video.
JVM, AOT und Native Image vergleichen
0:00 Native Image ist das Thema, das in Quarkus-Vorträgen die meiste Aufmerksamkeit bekommt — und in Projekten die meisten Enttäuschungen produziert. Nicht weil die Technik schlecht wäre, sondern weil sie als Ziel behandelt wird statt als Option mit Preisschild. Der Preis steht in der Buildzeit, in den Buildressourcen und in einer strengeren Haltung gegenüber allem Dynamischen.
0:21 In diesem Modul sehen wir uns beides an: was Sie bekommen und was es kostet. Und am Ende treffen Sie eine begründete Entscheidung — die durchaus gegen Native ausfallen darf.
JVM, AOT und Native Image vergleichen
0:31 Vorletztes Fachmodul. Mit den Zahlen aus den letzten beiden Modulen im Rücken ist das hier keine Glaubensfrage mehr, sondern eine Rechnung. Danach folgen die beiden Module, in denen alles zusammenläuft: die eigentliche Migration und die Roadmap für das, was danach kommt.
Was AOT und Native Image bedeuten
0:47 Beginnen wir mit der Einordnung. Was passiert technisch, und was davon ist eigentlich Quarkus-spezifisch? Ein Native Executable enthält nur den Code, der zur Laufzeit erreichbar ist, samt vorbereiteter Startzustände. Quarkus liefert die nötigen Metadaten aus dem Build-Time-Processing, das wir in Modul zwei besprochen haben — die beiden Themen hängen also direkt zusammen.
1:10 Und ein Punkt der Fairness: Native Images sind kein Alleinstellungsmerkmal von Quarkus. Auch Spring Boot unterstützt AOT-verarbeitete Native Images samt zugehöriger Tests. Wer also mit Native argumentiert, argumentiert nicht automatisch für einen Plattformwechsel. Drei Schichten, drei Kompromisse. Ganz oben der klassische JVM-Betrieb: schneller Build, volle Dynamik, dafür eine Aufwärmphase.
1:34 In der Mitte die vorverarbeitete Variante — Arbeit wandert in den Build, der Start wird kürzer. Unten das Native Executable: kein Aufwärmen, wenig Speicher, dafür strenge Regeln. Wichtig ist die unterste Zeile: Es ist dieselbe Anwendung. Sie entscheiden nicht über den Code, sondern über die Auslieferung. Das macht die Entscheidung reversibel — und deshalb weniger dramatisch, als sie oft verhandelt wird.
2:01 Drei Befehle, und der dritte ist der praktisch wichtigste: der Build im Container. Damit brauchen Sie lokal keine passende GraalVM-Installation, und vor allem bauen alle im Team und in der Pipeline mit derselben Version. Genau das ist bei Native Builds wichtiger als sonst, weil die Version das Ergebnis beeinflusst. Und die Fußnote nennt die Mindestanforderung: Seit Quarkus 3.36 ist Version 25 die minimal offiziell unterstützte GraalVM- beziehungsweise Mandrel-Version.
2:29 Wer noch mit älteren Ständen arbeitet, sollte das vor dem ersten Versuch klären. Der erste Punkt ist ein Planungsfehler: Native wird zum Standardweg erklärt, ohne dass jemand die Buildzeit eingeplant hat — und dann steht die Pipeline. Der zweite ist die zu alte lokale Installation. Der dritte ist ein Wiederholbarkeitsproblem: Ohne Container-Build hängt das Ergebnis an der Maschine, auf der gebaut wurde.
2:52 Und der vierte ist der rote Faden dieses Seminars: Der Vorteil wird angenommen statt gemessen. Gerade bei Native lohnt sich das Messen besonders, weil die Unterschiede je nach Anwendung stark schwanken.
Was ein Native Build kostet
3:05 Reden wir über den Preis. Er besteht aus zwei Teilen: Zeit und Ressourcen beim Bauen — und einer anderen Haltung gegenüber allem Dynamischen. Ein Native Build dauert typischerweise mehrere Minuten und braucht mehrere Gigabyte Arbeitsspeicher, während ein JVM-Build im Sekundenbereich bleibt. Das ist kein Detail, das ist ein Unterschied in der Größenordnung — und er trifft jeden Pipeline-Lauf.
3:29 Der zweite Teil betrifft die Dynamik: Was zur Laufzeit reflektiv geladen wird, muss ausdrücklich bekannt gemacht werden. Das Bild: Sie packen für eine Expedition, bei der es unterwegs keine Läden gibt. Was Sie nicht eingepackt haben, fehlt — und zwar endgültig. Die Tabelle bringt den Preis auf den Punkt. Achten Sie besonders auf die letzte Zeile: Ein Native Image lässt sich nur über Integrationstests prüfen, weil der Test in einem eigenen Prozess läuft.
3:55 Das ist der Grund, warum wir in Modul neun so viel Wert auf diese Testebene gelegt haben — ohne sie können Sie ein Native Image nicht sinnvoll absichern. Und die Fußnote klärt eine Verwirrung, die in älteren Beispielen auftaucht: Die frühere Native-Testannotation gibt es nicht mehr, sie wurde durch die allgemeine Integrationstest-Annotation ersetzt.
4:15 Vier Punkte zur Verträglichkeit. Reflektiver Zugriff muss registriert werden, ebenso dynamische Proxys und mitzuliefernde Ressourcen. Der dritte Punkt ist der, den man sich merken sollte: Was in der JVM funktioniert, kann nativ fehlen — umgekehrt aber nie. Ein Native Image ist also immer eine Teilmenge, nie eine Erweiterung. Deshalb der vierte Punkt: Jede Fremdbibliothek gehört vor der Entscheidung geprüft, nicht danach.
4:41 Der Aufwand für diese Prüfung ist überschaubar, der Aufwand für eine abgebrochene Native-Umstellung nicht. Der erste Punkt ist ein Ablaufproblem: Der Native Build läuft erst in der Pipeline und blockiert dort minutenlang — merken Sie sich, einmal lokal zu bauen, bevor Sie andere warten lassen. Der zweite ist die gefährlichste Lücke: ein Fehler, der nur nativ auftritt, und kein Test, der ihn findet.
5:04 Der dritte sind fehlende Ressourcen im Image, typischerweise Templates oder Zertifikate. Und der vierte ist ein Infrastrukturthema: Der Speicherbedarf des Builds sprengt den Runner — was als kryptischer Abbruch erscheint und nicht als klare Meldung.
Entscheidung anhand von Messwerten
5:19 Damit kommen wir zur Entscheidung. Und die ist erfreulich unspektakulär, wenn man die Zahlen aus den letzten beiden Modulen hat. Die Entscheidung ist eine Abwägung zwischen Laufzeitgewinn und Buildkosten. Zwei Dinge sind daran wichtig. Erstens fällt sie je Dienst, nicht je Plattform — ein Dienst, der auf null skaliert, profitiert ganz anders als ein Dauerläufer.
5:41 Zweitens fällt sie mit Zahlen aus Ihrer Anwendung, nicht aus einem Blogbeitrag. Die Spannbreite zwischen verschiedenen Anwendungen ist erheblich, und genau deshalb sind fremde Messwerte hier so wenig wert. Und weil die Entscheidung je Dienst fällt, darf sie auch unterschiedlich ausfallen: JVM für den Dauerläufer, Native für den Dienst, der auf null skaliert.
6:01 Das ist kein Widerspruch, sondern der Sinn der Übung. Zwei Spalten, und keine davon ist die Verliererspalte. Links stehen Argumente, die im Entwicklungsalltag zählen: kurze Builds, volle Dynamik, vertraute Fehlersuche. Rechts Argumente, die im Betrieb zählen: kurze Startzeit, wenig Speicher. Die dritte Zeile ist die Entscheidungshilfe — Dauerläufer links, häufig startende Workloads rechts.
6:26 Und die vierte Zeile erinnert an eine Kostenposition, die gern vergessen wird: Native braucht zusätzliche Tests, sonst prüfen Sie ein Artefakt, das Sie nie ausgeführt haben. Fünf Schritte, und vier davon haben Sie in den letzten Modulen schon geübt. Neu ist der vierte: die Bibliotheken auf Native-Tauglichkeit prüfen. Nehmen Sie sich dafür die Liste aus der Inventur, die wir im nächsten Modul ohnehin brauchen.
6:51 Und dann der fünfte Schritt: eine Empfehlung schreiben, mit den Zahlen, auf denen sie beruht. Die Fußnote ist mir wichtig: Fällt die Empfehlung gegen Native aus, ist das ein Ergebnis. Es bedeutet, dass Sie geprüft haben — und das ist mehr, als die meisten Projekte vorweisen können. Der erste Punkt ist die häufigste Lücke in der Rechnung: Die Buildzeit taucht gar nicht auf, obwohl sie das Team täglich trifft.
7:15 Der zweite ist die falsche Messgröße — Kaltstart bei einem Dienst, der monatelang läuft. Der dritte ist ein Motivationsproblem: Native wird gewählt, weil es zum Bild einer modernen Migration passt. Und der vierte ist eine unzulässige Verallgemeinerung: Die Entscheidung gilt pauschal für alle Dienste des Teams, obwohl sich deren Lastprofile stark unterscheiden.
Übung
7:36 In der Übung bauen Sie beide Varianten, prüfen beide mit derselben Suite und schreiben eine Empfehlung. Der Hinweis auf der Folie ist praktisch gemeint: Planen Sie für den Native Build Zeit und Speicher ein — mehrere Minuten und mehrere Gigabyte sind normal, nicht Anzeichen eines Fehlers. Erfolgreich sind Sie, wenn beide Varianten dieselben Tests bestehen und eine Tabelle vier Zahlen nennt: Startzeit, Speicherbedarf, Buildzeit und Containergröße — jeweils mit den Messbedingungen.
8:04 Diese Tabelle ist das Ergebnis, nicht das Native Image selbst. Wer Zeit übrig hat, notiert zusätzlich, welche Bibliotheken beim Native Build angeeckt sind — diese Liste ist für die übrigen Dienste Ihres Teams wertvoller als die Messwerte selbst. Der dritte Schritt ist der, den man am ehesten weglässt und am wenigsten weglassen sollte: die Integrationstests gegen das Native Image.
8:27 Ohne sie haben Sie ein Artefakt gebaut, aber nicht geprüft. Der vierte Schritt sammelt die Zahlen, der fünfte formuliert die Empfehlung — und zwar je Dienst. Die Fußnote sagt, wohin das Ergebnis wandert: in die Entscheidungsvorlage, die Sie im letzten Modul schreiben. Alles, was Sie hier sauber erheben, spart dort Diskussion.
8:47 Der erste Punkt macht die ganze Übung wertlos: Die Testsuite läuft nur gegen die JVM-Variante. Der zweite ist die bekannte Bedingungsfalle — unterschiedliche Messbedingungen zwischen beiden Läufen. Der dritte ist ein Umgang mit Widerständen: Der Native Build bricht ab, und die Ursache wird nicht zu Ende verfolgt; dabei ist genau diese Ursache eine wertvolle Information für Ihre Entscheidung.
9:09 Und der vierte ist der ehrlichste Punkt dieser Folie: Die Empfehlung steht fest, bevor die Zahlen vorliegen. Dann können Sie sich das Messen auch sparen.
Lieber mit Trainer? Dieses Modul ist Teil unserer Team-Schulungen — mit Übungen, Ihrem eigenen Code und Fragen, die ein Video nicht beantwortet. Mehr erfahren →