GraalVM Native Image ile Spring Boot: Ne Zaman Değer?
'Uygulama 60 milisaniyede açılıyor' cümlesi etkileyici; peki bedeli ne? GraalVM native image'ı JVM ve Java 25'in AOT önbelleğiyle başlangıç, bellek, tepe verim ve uyumluluk açısından karşılaştırıyor; hangi senaryolarda gerçekten değdiğini, Spring Boot ile adım adım derlemeyi, RuntimeHints ve tracing agent'ı, derleme zamanında sabitlenen profiller gibi tuzakları ve karar rehberini anlatıyoruz.
"Spring Boot uygulaması 60 milisaniyede açılıyor, 80 MB bellekle çalışıyor." GraalVM native image sunumlarındaki bu cümle doğrudur ve etkileyicidir. Sunumda söylenmeyen kısım da doğrudur: derleme 5–10 dakika ve 8 GB RAM ister, yansıma (reflection) kullanan her kütüphane için meta veri gerekir, tepe verim JIT'li JVM'in gerisinde kalır ve profil gibi bazı kararlar derleme zamanında sabitlenir. Native image bir sihir değil, bir takastır; kurumsal ekipler için soru "yapabilir miyiz" değil, "hangi servis için değer" sorusudur.
Bu yazıda native image'ın ne yaptığını, JVM ve Java 25'in AOT önbelleğiyle karşılaştırmasını, değdiği ve değmediği senaryoları, Spring Boot ile adım adım derlemeyi, RuntimeHints ve tracing agent'ı, sahada karşılaşılan sorunları ve karar rehberini anlatıyoruz.
Native image ne yapar?
GraalVM native image, uygulamayı ve bağımlılıklarını derleme zamanında analiz eder ("kapalı dünya" varsayımı: çalışma zamanında yeni sınıf yüklenmez), erişilebilir olan her şeyi makine koduna çevirir ve JVM olmadan çalışan tek bir yürütülebilir dosya üretir. Sınıf yükleme, bytecode doğrulama ve JIT ısınması ortadan kalkar; başlangıç milisaniyelere iner, bellek küçülür. Bedeli, dinamik davranışın (yansıma, dinamik proxy, kaynak yükleme, serileştirme) derleme zamanında bildirilmesi gerekliliğidir. Spring Boot 3, Spring AOT motoruyla bean tanımlarını derleme zamanında çözer ve çerçevenin ihtiyaç duyduğu meta verinin çoğunu otomatik üretir; kalan kısım uygulamanın ve üçüncü taraf kütüphanelerin sorumluluğudur.
Üç seçenek yan yana
Ölçüt | JVM (JIT) | JVM + CDS / AOT önbelleği (Java 25) | GraalVM native image |
|---|---|---|---|
Başlangıç (orta boy Spring Boot servisi) | 8–20 sn | 3–8 sn | 0,05–0,3 sn |
Bellek (RSS, boşta) | 300–600 MB | 250–500 MB | 60–150 MB |
Tepe verim | En yüksek (JIT profil odaklı optimizasyon) | Aynı | %10–40 düşük (PGO ile fark kapanır; Oracle GraalVM) |
Isınma | Dakikalar | Dakikalar | Yok; ilk istek tam hızda |
Derleme süresi / kaynağı | Saniyeler | Saniyeler + eğitim koşusu | 3–15 dk, 6–12 GB RAM |
Uyumluluk | Tam | Tam | Kapalı dünya: yansıma/proxy/kaynak için meta veri; bazı kütüphaneler desteklenmez |
Gözlemlenebilirlik | JFR, profiller, ajanlar, heap dump | Aynı | Sınırlı JFR, ajan yok; Micrometer/OTel çalışır |
GC | G1/ZGC/Shenandoah | Aynı | Serial (Community), G1 (Oracle GraalVM) |
Hata ayıklama | Kolay | Kolay | Zor; hata derleme zamanında ya da çalışma zamanında "sınıf bulunamadı" |
Kritik nokta: Java 24–25'in AOT önbelleği (Leyden) ile JVM başlangıcı zaten belirgin kısaldı. Çoğu kurumsal servis için "10 saniye → 4 saniye" yeterli iyileşmedir ve native image'ın uyumluluk bedelini ödemeyi gerektirmez (bkz. Java 25).
Ne zaman değer, ne zaman değmez?
Değer | Değmez |
|---|---|
Sıfıra ölçeklenen ortamlar: AWS Lambda, Cloud Run, Knative — her soğuk başlangıç kullanıcıya gecikme | Uzun süre çalışan, yüksek verim gerektiren servisler: JIT tepe verimde kazanır, başlangıç günde bir kez olur |
Çok sayıda küçük servis, düşük trafik: bellek kirası düşer, pod yoğunluğu artar | Yoğun yansıma/dinamik kütüphane kullanan uygulamalar (eski ORM'ler, script motorları, bazı SAP/JNI köprüleri) |
CLI araçları ve kısa ömürlü işler: kullanıcı başlangıcı bekler | CI kapasitesi sınırlı ekipler: her derleme dakikalar ve gigabaytlar |
Edge/IoT ve sınırlı bellek | Derin profil ve ajan bağımlılığı olan operasyon (APM ajanları, heap analizi) |
Hızlı yatay ölçekleme ve anında geri alma gereken servisler | Değişim hızı yüksek, çok üçüncü taraf bağımlılığı olan büyük monolitler |
Spring Boot ile adım adım
<!-- pom.xml: Spring Boot 3.x parent zaten AOT desteğini içerir; native profili GraalVM eklentisini ekler -->
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<configuration>
<buildArgs>
<buildArg>--no-fallback</buildArg>
<buildArg>-H:+ReportExceptionStackTraces</buildArg>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles># 1) GraalVM JDK ile yerel derleme (Oracle GraalVM ya da GraalVM Community / Liberica NIK)
./mvnw -Pnative native:compile
./target/siparis-servisi # 60–200 ms'de ayakta
# 2) GraalVM kurmadan Buildpacks ile konteyner imajı
./mvnw -Pnative spring-boot:build-image # BP_NATIVE_IMAGE=true; imaj ~100 MB, JVM gerektirmez
# 3) Native testler: JUnit testleri native yürütülebilirde koşar
./mvnw -PnativeTest testYansıma ve kaynaklar için ipuçları
// Jackson ile serileştirilen ama Spring'in göremediği tipler (örneğin yalnızca dinamik yüklenen DTO'lar)
@RegisterReflectionForBinding({FaturaDto.class, SapDonusKaydi.class})
@Configuration
class SerilestirmeAyari {}
// Daha genel: RuntimeHintsRegistrar ile yansıma, kaynak ve proxy kayıtları
@ImportRuntimeHints(EntegrasyonHintleri.class)
@Configuration
class EntegrasyonAyari {}
class EntegrasyonHintleri implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader cl) {
hints.reflection().registerType(EskiSistemIstemci.class, MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.DECLARED_FIELDS);
hints.resources().registerPattern("sablonlar/*.xml");
hints.proxies().registerJdkProxy(SiparisPort.class);
hints.serialization().registerType(TypeReference.of("com.acme.legacy.EskiKayit"));
}
}Hangi kütüphanenin ne istediğini elle bulmak yerine tracing agent kullanılır: uygulama JVM'de ajanla çalıştırılır, testler ve tipik akışlar koşulur, ajan yansıma/kaynak/proxy kullanımını META-INF/native-image/ altına JSON olarak yazar. GraalVM'in reachability metadata deposu yaygın kütüphaneler için hazır meta veri sağlar; Spring'in kendi ipuçları da otomatik gelir. Kalan boşluklar için yukarıdaki kayıtlar.
java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/acme/siparis-servisi \
-jar target/siparis-servisi.jar # ardından tüm akışları ve entegrasyon testlerini tetikleSahada karşılaşılan sorunlar
Profil ve koşullar derleme zamanında sabitlenir. Spring AOT,
@Profileve@Conditionalkararlarını derleme sırasında verir;prodprofiliyle derlenen imajtestprofilinde farklı bean'ler yükleyemez. Ortama göre değişen yapılandırma değerleri (URL, gizli bilgi) çalışma zamanında okunur; ama bean seti derlemede belirlenir. Ortam başına imaj ya da profil bağımsız tasarım gerekir.Çalışma zamanında "sınıf bulunamadı": Meta verisi olmayan yansıma. Tracing agent ve native testlerle derleme öncesi yakalanır; üretimde yakalanan her biri bir sürüm demektir.
Desteklenmeyen kütüphaneler: JNI tabanlı köprüler (SAP JCo gibi), bazı script motorları, eski dinamik proxy kütüphaneleri. Bu bağımlılığı olan servis JVM'de kalır.
Bellek yanılgısı: RSS düşer ama heap ihtiyacı aynı kalır;
-Xmxyine belirlenir. Community sürümde Serial GC vardır; yüksek tahsis hızında duraklamalar artar.Derleme altyapısı: CI çalıştırıcılarında 8–12 GB RAM; derleme süresi PR başına dakikalar. Native derleme genellikle yalnızca ana dal ve sürüm için, PR'larda JVM testleri.
Gözlemlenebilirlik boşluğu: APM ajanları çalışmaz; OpenTelemetry SDK ve Micrometer çalışır. Operasyon ekibinin araçları kontrol edilmeden geçilmez (bkz. observability).
Tepe verim sürprizi: Yük testinde JVM'in %20 önde çıkması normaldir; Oracle GraalVM'de PGO (profil güdümlü optimizasyon) farkı kapatır ama ek bir eğitim adımı ister.
Karar rehberi
Servis sıfıra ölçekleniyor ya da soğuk başlangıç kullanıcı deneyimini etkiliyor mu? Hayırsa önce Java 25 AOT önbelleği; muhtemelen yeterli.
Bağımlılık listesi native ile uyumlu mu? Tracing agent + native test ile bir günlük deneme cevap verir.
Operasyon araçları (profil, ajan, heap dump) native'de karşılanabiliyor mu?
CI derleme kapasitesi ve süresi kabul edilebilir mi?
Tepe verim kritik mi? Evetse JVM; ya da Oracle GraalVM + PGO ölçümü.
Pratikte tipik kurumsal manzara karmadır: fonksiyonlar, CLI araçları ve seyrek çağrılan küçük servisler native; çekirdek işlem servisleri JVM + AOT önbelleği. Aynı kod tabanı iki şekilde de derlenebilir; native uyumluluğu bir kez sağlanınca seçim servis başına verilir.
Sık sorulan sorular
CRaC bir alternatif mi?
Evet, farklı bir takasla: CRaC (Coordinated Restore at Checkpoint), ısınmış JVM'in anlık görüntüsünü alır ve saniyenin altında geri yükler; tam JVM uyumluluğu korunur, JIT kazancı kalır. Bedeli: Linux'a özgü, kontrol noktası alma disiplini (açık bağlantı ve dosyaların kapatılması; Spring Boot 3.2+ destekler), anlık görüntünün gizli bilgi içerebilmesi. Uyumluluk sorunlu ama başlangıç kritikse CRaC, native'den önce denenmelidir.
Oracle GraalVM mı, Community mi?
Community (ve Liberica NIK gibi dağıtımlar) ücretsiz ve çoğu senaryo için yeterlidir; Oracle GraalVM G1 GC, PGO ve daha iyi tepe performans sunar, lisans koşulları (GFTC) üretim kullanımına göre değerlendirilir.
Native image güvenlik açısından fark yaratır mı?
Saldırı yüzeyi küçülür (JVM ve sınıf yükleme yok, kapalı dünya), ama bağımlılık açıkları aynen derlenir; SBOM ve tarama yine gerekir. Yürütülebilir dosya yeniden derlenmeden yamalanamaz; yama süreci imaj yeniden derlemesidir.
Sonuç
GraalVM native image, doğru yerde olağanüstü bir araçtır: sıfıra ölçeklenen servisler, fonksiyonlar, CLI araçları ve yoğun küçük servis kümeleri. Yanlış yerde — yüksek verimli çekirdek servisler, yansıma yoğun bağımlılıklar, sınırlı CI — bedeli kazancından büyüktür. Java 25'in AOT önbelleği çoğu kurumsal servis için "yeterince hızlı başlangıcı" native'siz sağlar; native kararı ölçümle, servis başına ve tracing agent ile bir günlük deneme sonrasında verilir.
Servislerinizin başlangıç ve bellek profilini ölçmek, native uyumluluğunu değerlendirmek ya da doğru servisleri native'e taşımak için yazılım tasarımı ve mimari hizmetimize göz atabilirsiniz.