Native & Performance
GraalVM, startup optimization, memory footprint, AOT compilation
Native & Performance
A Spring performance tuning nem egyetlen kapcsoló, hanem trade-offok sorozata: startup idő, memória, throughput, operációs egyszerűség és fejlesztői rugalmasság között egyensúlyozol.
1. Definíció / Definition
Mi ez? / What is it?
A Native és Performance téma Springben arról szól, hogyan indul gyorsabban, fogyaszt kevesebb memóriát és viselkedik kiszámíthatóbban az alkalmazás. Ennek egyik fontos eszköze a GraalVM Native Image és a Spring Boot 3 beépített AOT támogatása, de ide tartozik a klasszikus JVM tuning is.
Miért létezik? / Why does it exist?
Mert nem ugyanazok a prioritások egy serverless functionnél, egy Kubernetes autoscalinges API-nál és egy nagy throughputú backendnél. Van, ahol a cold start kritikus, máshol a peak throughput vagy a GC viselkedés a fontosabb.
Hol helyezkedik el? / Where does it fit?
Ez a téma build-time, runtime és architektúra szint között helyezkedik el. Érinti a compilation modellt, a Spring auto-configurationt, a reflection használatot, a GC-t és újabban a virtual threadöket is.
2. Alapfogalmak / Core Concepts
2.1 JIT vs AOT
A klasszikus JVM JIT-fordítást használ. Az alkalmazás bytecode-ként indul, majd runtime közben a JVM optimalizálja a hotspotokat.
A Native Image ezzel szemben AOT módon fordít natív binárist.
| Modell | Előny | Hátrány |
|---|---|---|
| JIT | magas csúcsteljesítmény, rugalmas runtime | lassabb startup, nagyobb memória |
| AOT / Native | nagyon gyors startup, kisebb memória | gyengébb dinamizmus, build bonyolultabb |
2.2 Spring AOT
Spring Boot 3-tól az AOT támogatás beépítettebb és kiforrottabb. A framework előre elemzi a bean graph egy részét, generál kódot és csökkenti a runtime reflective munkát.
Ez azért fontos, mert a native fordítás rosszul szereti a “majd runtime kiderül” mintákat.
2.3 Reflection és dynamic proxy korlátok
A Native Image egyik tipikus fájdalma a reflection. Ha valami csak runtime derül ki és nincs hozzá metadata vagy hint, a natív bináris nem biztos, hogy tudni fog róla.
Érintett területek:
- reflection alapú object creation;
- dinamikus proxyk;
- serialization;
- classpath scanning szélsőséges dinamikus formái.
Régebben gyakori volt a reflect-config.json, újabb Spring verzióknál inkább RuntimeHints és AOT integráció a preferált út.
2.4 Startup idő és memória footprint
A native appok fő vonzereje:
- nagyon gyors startup;
- kisebb RSS / memória footprint;
- jobb skálázódás sok rövid életű instance esetén.
Ez különösen jó lehet serverless, CLI tooling vagy agresszíven autoscalelő workload esetén.
2.5 JVM tuning továbbra is fontos
Nem minden problémára a native a válasz. Sokszor elég:
- heap sizing pontosítása;
- megfelelő GC választás, például G1GC vagy ZGC;
- felesleges auto-configuration kikapcsolása;
- lazy initialization körültekintő használata.
2.6 Virtual threads
Project Loom és Spring Boot 3.2+ környékén a virtual threadök új lehetőséget adnak, főleg blocking IO workloadnál. Nem helyettesítik a native image-et, hanem más jellegű optimalizációt kínálnak: concurrency egyszerűsítést kisebb platform-thread költséggel.
3. Gyakorlati használat / Practical Usage
Ha van egy REST API-d, ami kicsi, sok példányban fut és gyakran skálázódik fel-le, akkor a native image komoly előny lehet. A pod gyorsabban áll fel, kevesebb memóriát kér, az autoscaling gyorsabban reagál. Itt a cold start és a footprint dominál.
Ha viszont van egy hosszú ideig futó, throughput-érzékeny monolitod, a klasszikus JIT JVM lehet jobb választás. A JIT képes futás közben agresszívebben optimalizálni a forró kódrészeket, így peak teljesítményben sokszor erős marad.
Spring alkalmazás indítását native nélkül is lehet javítani. Például ha fölöslegesen betöltesz JDBC, JPA, Security vagy templating auto-configot egy egyszerű service-be, az startup időt és memóriát is pazarol. A “kevesebb classpath, kevesebb autoconfig, kevesebb dinamizmus” sokszor már önmagában komoly nyereség.
Virtual threadökkel tipikus use case a sok blocking HTTP vagy DB hívás kezelése egyszerű programozási modellel. Nem kell rögtön reactive-re váltani csak a concurrency miatt, de mérni kell, mert nem minden dependency viselkedik egyformán.
4. Kód példák / Code Examples
4.1 RuntimeHints reflection helyett
@Configuration
@ImportRuntimeHints(CustomerRuntimeHints.class)
public class NativeHintsConfig {
}
class CustomerRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.reflection().registerType(CustomerDto.class,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_PUBLIC_METHODS,
MemberCategory.DECLARED_FIELDS);
}
}
record CustomerDto(Long id, String name) {}
4.2 Lazy initialization és auto-config kizárás
spring:
main:
lazy-initialization: true
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
Ez nem univerzális tuning, de bizonyos lightweight service-eknél startup időt csökkenthet.
4.3 Virtual thread executor
@Configuration
public class VirtualThreadConfig {
@Bean
public AsyncTaskExecutor applicationTaskExecutor() {
return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}
}
4.4 Maven native build plugin példa
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
5. Trade-offok / Trade-offs
Native előnyei
- gyors startup;
- kisebb memória footprint;
- jó sűrűség konténeres környezetben.
Native hátrányai
- hosszabb, komplexebb build;
- reflection és dynamic proxy korlátok;
- egyes library-k rosszabb kompatibilitása;
- throughput nem mindig jobb, néha gyengébb.
JVM tuning előnyei
- kisebb migrációs kockázat;
- jobb library kompatibilitás;
- erős throughput és kiforrott operáció.
Virtual thread trade-off
- egyszerű blocking modell nagy concurrencyvel;
- de nem old meg minden CPU, lock vagy downstream limitációt.
6. Gyakori hibák / Common Mistakes
6.1 Native-et univerzális gyorsításnak hinni
A native nem mindenre jobb. Startup és memória terén erős, de throughputban vagy build complexityben lehet rosszabb.
6.2 Reflection-heavy design fenntartása változatlanul
Ha az app dinamikus class loadingra, reflectionre és varázslatos runtime discoveryre épít, a native migráció fájdalmas lesz. Érdemes egyszerűsíteni a mintákat.
6.3 Vak lazy initialization
A lazy init csökkentheti a startupot, de átrakhat költséget az első requestre. Ez hideg útvonalon oké lehet, kritikus endpointnál viszont csúnya latency spike-ot okozhat.
6.4 GC tuning mérés nélkül
A G1GC és ZGC közti váltás nem hitkérdés. Workload alapján kell mérni: latency profil, heap méret, allocation rate számít.
6.5 Virtual thread túlromantizálása
A virtual thread nem helyettesíti a connection poolt, a downstream rate limitet vagy a rosszul skálázott SQL-t. Egyszerűsítheti a modellt, de nem törli el a rendszerhatárokat.
7. Senior szintű meglátások / Senior-level Insights
Senior szinten a performance tuning mindig workload-specifikus. Nem “mi a legmodernebb”, hanem “mi a legjobb ehhez az üzleti profilhoz”. Rövid életű, burstös workloadra a native fantasztikus lehet. Hosszú életű, számításigényes szolgáltatásnál lehet, hogy marad a JIT JVM.
A startup optimalizálás gyakran sokkal olcsóbb application hygiene-ből, mint platformváltásból. Felesleges starterek kiszedése, autoconfig visszafogása, bean graph egyszerűsítése, kevesebb reflection: ezekkel sokat lehet nyerni még native nélkül is.
A kompatibilitási kockázatot ne becsüld alá. Egy library, ami remekül működik HotSpoton, natív környezetben ütközhet serialization, proxy vagy resource loading problémába. Ezért a native adoptálás inkább termék- és platformdöntés, mint puszta build flag.
Virtual threadöknél a senior hozzáállás az, hogy új concurrency eszköz, nem vallás. Sok blocking IO use case-et szebben old meg, de továbbra is kell timeout, bulkhead, connection pool és monitoring. A könnyebb programozási modell nem ment fel a rendszerszintű gondolkodás alól.
8. Szószedet / Glossary
- GraalVM Native Image: natív binárist készítő AOT technológia.
- AOT: ahead-of-time fordítás.
- JIT: just-in-time runtime optimalizáció.
- Spring AOT: Spring build-time optimalizációs és metadata generáló mechanizmusa.
- RuntimeHints: Spring által használt native metadata API.
- Reflection: típusok és tagok runtime introspekciója.
- GC: garbage collector.
- G1GC: általános célú modern HotSpot garbage collector.
- ZGC: nagyon alacsony pause-időre optimalizált GC.
- Virtual thread: könnyűsúlyú thread modell a Loomból.
9. Gyorsreferencia / Cheatsheet
| Téma | Mikor jó | Mire figyelj |
|---|---|---|
| Native Image | cold start és memória fontos | build idő, kompatibilitás |
| JIT JVM | hosszú életű, throughput heavy app | startup és memória |
| Spring AOT | native readiness és gyorsabb init | dinamikus minták csökkentése |
| RuntimeHints | reflection metadata | csak szükséges típust regisztrálj |
| lazy init | startup csökkentés | első request büntetés |
| autoconfig exclude | karcsúbb app | ne zárj ki szükséges modult |
| G1GC | jó default sok esetben | mérj heap és pause alapján |
| ZGC | nagyon alacsony latency célokra | memória és verziófüggés |
| virtual threads | sok blocking IO | downstream limit továbbra is számít |
🎮 Játékok
8 kérdés