Haladó Olvasási idő: ~7 perc

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