"Runtime Yılı" Manifestosu: JDK 28'de Project Valhalla (JEP 401 & 539), Project Leyden (JEP 544) ve JVM'in Fizik Kurallarını Değiştiren Mimari Evrim
Ron Pressler'ın "Java - The Year of the Runtime" vizyonu doğrultusunda JDK 27 ve JDK 28 ile ana hatta (mainline) giren JEP 401 (Value Objects), JEP 539 (Strict Field Initialization) ve JEP 544 (AOT Code Compilation) gelişmelerinin bellek yerleşimi, CPU önbellek (L1/L2) verimliliği ve kurumsal mikroservis mimarileri üzerindeki etkilerini inceliyoruz.
Yapay zeka kodlama ajanlarının (AI coding agents) sözdizimsel (syntactic) tartışmaları ikinci plana ittiği bir çağda, kurumsal yazılım mimarisinin asıl ağırlık merkezi yeniden çalışma zamanı (runtime) fiziğine kayıyor. Geçtiğimiz günlerde OpenJDK ekibinden Ron Pressler’ın Inside.java üzerinden yayımlanan "Java - The Year of the Runtime!" başlıklı sunumu, tam olarak bu paradigma değişimini özetliyordu: Kodu kimin veya hangi ajanın yazdığından bağımsız olarak, üretim ortamında faturayı belirleyen şey çalışma zamanının bellek yoğunluğu, gözlemlenebilirliği (observability), çöp toplama (GC) deterministikliği ve soğuk başlatma (cold-start) hızıdır.
Otuz yılı aşkın süredir Java Sanal Makinesi (JVM), kurumsal dünyaya muazzam bir dinamizm ve geriye dönük uyumluluk sundu. Ancak bu dinamizmin donanım seviyesinde ağır bir bedeli vardı: Her nesnenin bir kimliği (identity) olması zorunluluğu (Object Header ve pointer indirection maliyeti) ve JIT (Just-In-Time) derleyicisinin ısınma (warm-up) süresi.
Eylül 2026'da genel kullanıma sunulan JDK 27 ve Ekim 2026 itibarıyla erken erişim (Early-Access) derlemeleri yayınlanan JDK 28, JVM'in bu iki tarihsel darboğazını kökten çözen üç dev mühendislik projesinin (Valhalla, Leyden ve Lilliput) nihayet ana hatta (mainline) birleştiği tarihi bir kırılma noktasını temsil ediyor. 1.816 dosyada 197.000 satırdan fazla kod değişikliğiyle OpenJDK ana deposuna entegre edilen JEP 401 (Value Objects), onun JVM doğrulama omurgasını oluşturan JEP 539 (Strict Field Initialization) ve HotSpot AOT önbelleğine doğrudan makine kodu derlemesi getiren JEP 544 (Ahead-of-Time Code Compilation), Java'nın kurumsal mikroservislerdeki çalışma fiziğini yeniden tanımlıyor.
Bu makalede; sözdizimi makyajlarının ötesine geçerek JDK 27 ve JDK 28'in kaputunun altında gerçekleşen bu büyük çalışma zamanı devrimini, bellek hizalaması (memory layout), CPU önbellek isabet oranı (cache locality) ve bulut-yerel (cloud-native) Spring Boot dağıtımları açısından derinlemesine inceleyeceğiz.
1. Bellek Fiziğini Yeniden Yazmak: Project Lilliput (JEP 534) ve Project Valhalla (JEP 401)
Bir Java mimarının üretim ortamında karşılaştığı en sinsi maliyet CPU değil, bellek bant genişliği (memory bandwidth) ve Pointer Chasing (işaretçi takibi) kaynaklı CPU döngü kayıplarıdır. Geleneksel JVM nesne modelinde, heap üzerindeki en küçük nesne bile bir Object Header taşır.
JDK 27 ile birlikte JEP 534 (Compact Object Headers by Default) devreye girerek Project Lilliput'un hedefini varsayılan hale getirdi: 64-bit mimarilerde 96 ila 128 bit yer kaplayan nesne başlığı (Mark Word + Klass Pointer), ek bir JVM bayrağına gerek kalmaksızın varsayılan olarak 64-bit'e (8 byte) indirildi. Bu değişiklik tek başına tipik kurumsal mikroservislerin canlı heap boyutunu %10 ile %20 arasında küçülttü.
Ancak asıl devrim, on yılı aşkın bir Ar-Ge sürecinin ardından JDK 28 ana hattına entegre edilen Project Valhalla ve onun merkezindeki JEP 401: Value Objects (Preview) ile gerçekleşti.
Kimlik Tuzağı (The Identity Trap) ve İşaretçi Dolaylaması
Java'nın ilk gününden beri tip sistemi keskin bir uçurumla ikiye ayrılmıştı:
- İlkel Tipler (
int,double,long): Değeriyle saklanır, kimliği yoktur, başlık (header) taşımaz, yığın (stack) veya CPU yazmaçlarında (registers) düz (flat) olarak tutulur. - Referans Tipleri (
Object,LocalDate,BigDecimal,Optional): Heap üzerinde ayrı bir bellek adresinde yaşar, benzersiz bir kimliği vardır (synchronizedbloğunda kilitlenebilir), nesne başlığı taşır ve diğer nesneler tarafından 64-bit veya sıkıştırılmış 32-bit referanslarla (pointer) işaret edilir.
Finansal bir mikroserviste bir milyon adet MonetaryAmount nesnesinden oluşan bir dizi (MonetaryAmount[]) tuttuğunuzu düşünün. Geleneksel JVM'de bu dizi, bellekte yan yana duran değerler değil; heap'in rastgele bölgelerine dağılmış bir milyon ayrı nesneye işaret eden bir referanslar dizisidir. CPU bu dizi üzerinde iterasyon yaparken her adımda farklı bir bellek adresine sıçrar ve L1/L2 önbellek ıskalaması (cache miss) yaşar.
"Mimari Not: Modern bir işlemcide L1 önbellekten veri okumak ~1 nanosananiye sürerken, ana bellekten (DRAM) pointer takibiyle veri çekmek ~60-100 nanosaniye sürer. Project Valhalla'nın çözdüğü asıl problem çöp toplayıcıyı rahatlatmaktan ziyade, JVM nesnelerini CPU önbellek hatlarına (cache lines) sığacak şekilde düzleştirmektir (heap flattening)."
JEP 401 ile value class ve value record Semantiği
JEP 401, nesne yönelimli programlamanın soyutlama gücünü (metotlar, arayüzler, kapsülleme) ilkel tiplerin performans karakteristiğiyle birleştiren value anahtar kelimesini getiriyor. Bir sınıf veya record, value olarak işaretlendiğinde kimliğinden (identity) feragat eder.
package com.tastutanlabs.finance.domain;
import java.math.BigDecimal;
import java.util.Objects;
/**
* JDK 28 - JEP 401 (Preview)
* Kimliksiz (Identity-free), değişmez (immutable) ve düzleştirilebilir değer kaydı.
*/
public value record MonetaryAmount(long minorUnits, CurrencyCode currency) {
public MonetaryAmount {
Objects.requireNonNull(currency, "Para birimi boş olamaz");
}
public MonetaryAmount add(MonetaryAmount other) {
if (this.currency != other.currency) {
throw new IllegalArgumentException("Farklı para birimleri toplanamaz: "
+ this.currency + " vs " + other.currency);
}
// Yeni bir 'value object' döner; ancak JIT bunu heap tahsisi (allocation)
// yapmadan doğrudan CPU register'ları üzerinden skalarize edebilir!
return new MonetaryAmount(Math.addExact(this.minorUnits, other.minorUnits), this.currency);
}
}
public value class CurrencyCode {
private final short isoNumericCode; // Örn: TRY = 949, USD = 840
public CurrencyCode(int code) {
if (code <= 0 || code > 999) {
throw new IllegalArgumentException("Geçersiz ISO 4217 kodu: " + code);
}
this.isoNumericCode = (short) code;
}
public short code() {
return isoNumericCode;
}
}
Bir sınıfı value class veya value record yaptığınızda JVM çalışma zamanında şu kurallar devreye girer:
- Tüm alanlar örtük olarak
final'dır: Durumu (state) oluşturulduktan sonra kesinlikle değiştirilemez. - Referans eşitliği (
==) durum eşitliğine dönüşür: Geleneksel sınıflarda==bellek adreslerini karşılaştırırken, değer nesnelerinde (value objects)==operatörü iki nesnenin alanlarının bit düzeyinde aynı olup olmadığını kontrol eder.new MonetaryAmount(100_00L, tryCode) == new MonetaryAmount(100_00L, tryCode)ifadesi her zamantruedöner! - Senkronizasyon yasaktır: Kimliği olmayan bir nesnenin monitörü de olamaz.
synchronized(amount)veyaSystem.identityHashCode(amount)gibi kimlik bağımlı işlemler derleme veya çalışma zamanında reddedilir. - Skalarizasyon (Scalarization) ve Heap Düzleştirme (Flattening): HotSpot JIT derleyicisi, metodun içindeki bir
MonetaryAmountnesnesini heap'te hiç oluşturmadan doğrudanlongveshortolarak iki ayrı CPU yazmacına (register) parçalar. Ayrıca bu nesneleri içeren dizilerde nesne başlıklarını (Object Header) ve referans işaretçilerini tamamen kaldırarak veriyi bellekte bitişik (contiguous) olarak hizalar.
JDK 28 ile birlikte standart kütüphanedeki Integer, Long, LocalDate, Optional gibi "değer tabanlı sınıflar" (value-based classes) da otomatik olarak değer sınıflarına (value class) dönüştürülmüştür.
2. final Alanların Kirli Sırrı ve JEP 539: Strict Field Initialization
Project Valhalla'nın neden 10 yıldan uzun sürdüğünü anlamak için JDK 28'e JEP 401 ile birlikte entegre edilen JEP 539: Strict Field Initialization in the JVM (Preview) önerisine bakmamız gerekir.
Çoğu kıdemli Java geliştiricisinin bile gözden kaçırdığı bir JVM gerçeği vardır: Java'da hiçbir alan (final olsa dahi) hayata sizin atadığınız değerle başlamaz. Nesne bellekte tahsis edildiği anda JVM tüm alanlara önce varsayılan sıfır değerlerini (0, false, null) yazar; ardından yapıcı metot (<init>) çalışarak gerçek değerleri atar.
Eğer yapıcı metot tamamlanmadan önce this referansı dışarı sızarsa (escaping this), üst sınıfın yapıcısından aşırı yüklenmiş (overridden) bir alt sınıf metodu çağrılırsa veya statik başlatıcılar (<clinit>) arasında döngüsel bir bağımlılık oluşursa, JVM final olarak işaretlenmiş bir alanın bile önce 0/null, milisaniyeler sonra ise gerçek değerini döndürdüğünü gözlemleyebilir.
// Geleneksel JVM'de final alanların sızıntı (mutability leak) problemi:
public class ConfigurationLoader {
public static final int DEFAULT_TIMEOUT = AuditLogger. resolveTimeout();
}
public class AuditLogger {
// Döngüsel başlatma sırasında ConfigurationLoader.DEFAULT_TIMEOUT henüz
// atanmadığı için 'final' olmasına rağmen 0 olarak okunur!
private static final String PREFIX = "[Timeout=" + ConfigurationLoader.DEFAULT_TIMEOUT + "] ";
public static int resolveTimeout() {
System.out.println(PREFIX + "Yapılandırma yükleniyor...");
return 5000;
}
}
Değer nesnelerinde (value class) bu durum kabul edilemez bir güvenlik ve bellek bütünlüğü açığıdır. Çünkü kimliği olmayan ve bellekte düzleştirilen (flattened) bir değer nesnesinin "yarı başlatılmış" (half-initialized) bir durumu olamaz.
ACC_STRICT_INIT Bayrağı ve Erken Başlatma (Early Construction)
JEP 539, sınıf dosyası (class file) formatına yeni bir ACC_STRICT_INIT (0x0800) bayrağı ve gelişmiş bir Bytecode Verifier (doğrulayıcı) kural seti ekler. Bir alan ACC_STRICT_INIT ile işaretlendiğinde:
- JVM Bytecode doğrulayıcısı, nesne örneği (
this) herhangi bir kod tarafından okunabilir hale gelmeden önce (yanisuper()çağrısı tamamlanmadan önceki erken yapım aşamasında - early larval state) tüm sıkı (strict) alanların kesin olarak başlatıldığını garanti eder. - Varsayılan
0veyanulldeğerinin gözlemlenmesi matematiksel olarak imkansız hale gelir. Statik başlatmada döngüsel bir okuma girişimi olursa, sessizce0dönmek yerine anında deterministik bir hata fırlatılır.
JEP 401'deki tüm value class alanları arka planda otomatik olarak ACC_STRICT_INIT bayrağını kullanır. Bu sayede JIT derleyicisi, nesnenin hiçbir zaman mutasyona uğramayacağından %100 emin olarak agresif bellek düzleştirme (flattening) optimizasyonlarını güvenle uygular.
3. GraalVM'in Acısı Olmadan Anında Başlatma: Project Leyden ve JEP 544
Valhalla ve Lilliput bellek yoğunluğunu ve çalışma zamanı (peak throughput) performansını çözerken, kurumsal Kubernetes kümelerindeki diğer büyük dert soğuk başlatma (cold-start) ve ısınma (warm-up) süresidir.
Yıllarca bu soruna verilen standart yanıt GraalVM Native Image oldu. Ancak hepimizin üretim ortamlarında deneyimlediği üzere, GraalVM Native Image "Kapalı Dünya Varsayımı" (Closed-World Assumption) ile çalışır: Çalışma zamanında dinamik sınıf yükleme yasaktır, Reflection, Dynamic Proxy ve Bytecode Generation için karmaşık JSON meta verileri gerekir, derleme süreleri CI/CD hatlarında dakikalar sürer ve en önemlisi C2 JIT derleyicisinin profil destekli optimizasyonlarından (Profile-Guided Optimization) mahrum kalındığı için tepe performansı (peak throughput) standart HotSpot JVM'in gerisinde kalabilir.
Project Leyden, JVM'i ortadan kaldırmak yerine hesaplamayı zamanda geriye kaydırarak (shifting computation in time) bu ikilemi bitiriyor. Ve bu yolculuğun en kritik halkası olan JEP 544: Ahead-of-Time Code Compilation, JDK 28 için resmi olarak hedeflendi (Targeted).
Katman Katman Büyüyen HotSpot AOT Önbelleği
OpenJDK ekibinin JDK 24'ten JDK 28'e kadar ilmek ilmek ördüğü AOT mimarisinin evrimine bakalım:
| JDK Sürümü | JEP Numarası | Mimari Yetenek | Üretim Etkisi |
|---|---|---|---|
| JDK 24 | JEP 483 | AOT Class Loading & Linking | Sınıflar okunmuş, ayrıştırılmış ve bağlanmış (linked) halde önbelleğe alınır. Başlatma süresinde ~%42 iyileşme. |
| JDK 25 (LTS) | JEP 514 & 515 | AOT Ergonomics & Method Profiling | Tek komutla AOT önbellek üretimi ve metot çağrı istatistiklerinin (profil verilerinin) önbelleğe kaydedilmesi. |
| JDK 26 | JEP 516 | AOT Object Caching with Any GC | AOT önbelleğindeki nesne grafiklerinin Generational ZGC dahil tüm çöp toplayıcılarla uyumlu çalışması. |
| JDK 28 | JEP 544 | Ahead-of-Time Code Compilation | Sık kullanılan metotların doğrudan optimize edilmiş makine koduna (native code) derlenip AOT önbelleğine gömülmesi. |
JEP 544 Üretimde Nasıl Çalışır?
JEP 544 ile birlikte, uygulamanızın kısa bir eğitim çalışması (training run) sırasında toplanan metot profilleri (JEP 515) kullanılarak, sıcak metotlar doğrudan yerel makine koduna derlenir ve .aot önbellek dosyasına yazılır. Üretimdeki pod ayağa kalktığında, HotSpot JVM yorumlayıcı (interpreter) veya C1 derleme aşamalarıyla vakit kaybetmeden sıfırıncı saniyede doğrudan optimize edilmiş yerel makine kodunu çalıştırmaya başlar.
Üstelik GraalVM'den farklı olarak Açık Dünya (Open-World) garantisi devam eder: Çalışma zamanında dinamik olarak yeni bir sınıf yüklenirse veya trafik karakteristiği değişirse, HotSpot JIT derleyicisi kaldığı yerden devreye girerek kodu yeniden optimize (deoptimize/re-JIT) edebilir!
Spring Boot 4.x tabanlı bir kurumsal mikroserviste JDK 28 AOT iş akışı şu kadar yalındır:
package com.tastutanlabs.order.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
/**
* CI/CD hattında AOT Training Run sırasında bağlam (context)
* ve kritik kod yolları ısındıktan sonra temiz çıkış yapılmasını sağlar.
*/
@Bean
public ApplicationListener<ApplicationReadyEvent> aotTrainingWarmupListener() {
return event -> {
if (Boolean.getBoolean("app.aot.training")) {
// Kritik serileştirme ve doğrulama yollarını ısıt (Warm-up)
WarmupVerifier.executeSyntheticWorkload();
System.out.println("AOT eğitim turu tamamlandı, önbellek dökümü için çıkılıyor.");
System.exit(0);
}
};
}
}
CI/CD (Docker multi-stage build) aşamasında tek komutla hem sınıf bağlama, hem metot profilleme hem de JEP 544 makine kodu derlemesi gerçekleştirilir:
# 1. Aşama (CI/CD Build): Tek komutla AOT Önbelleği ve Yerel Kod (JEP 544) Üretimi
java -XX:AOTCacheOutput=order-service.aot \
-Dapp.aot.training=true \
-jar order-service.jar
# 2. Aşama (Production Kubernetes Pod): Anında Optimize Yerel Kod ve ZGC ile Başlatma
java -XX:AOTCache=order-service.aot \
-XX:+UseZGC \
--enable-preview \
-jar order-service.jar
Bu mimari sayesinde Kubernetes Horizontal Pod Autoscaler (HPA) ani bir trafik artışında yeni pod'ları devreye aldığında, yeni kalkan Spring Boot pod'larında eskiden sıkça gördüğümüz "ilk 30 saniyedeki yüksek CPU kullanımı ve p99 gecikme sıçraması (warm-up latency spike)" tamamen tarihe karışıyor.
4. Kurumsal Backend Ekipleri İçin Mimari Geçiş Rehberi
JDK 27 ve JDK 28'in getirdiği bu çalışma zamanı devrimini kendi mühendislik organizasyonunuza adapte ederken bugün atmanız gereken somut adımlar şunlardır:
- Kimlik Bağımlı Kodları (Identity-Sensitive Code) Temizleyin:
Kod tabanınızdanew Integer(),synchronized(someValueObject)veya değer nesneleri üzerinde==ile referans kontrolü yapan eski alışkanlıkları derhal temizleyin. JDK 21+ derleyicilerindeki-Xlint:identityuyarılarını CI/CD hatlarınızda kırıcı hata (-Werror) olarak yapılandırın. - DDD Value Object'lerinizi
record'lara Dönüştürün:
Domain-Driven Design (DDD) mimarilerinizdekiMoney,Coordinates,TaxRate,CustomerIdgibi değişmez (immutable) sınıflarınızı şimdiden Javarecordyapısına geçirin. JDK 28 ve sonrasındaki LTS sürümüne (JDK 29) geçtiğinizde tek yapmanız gerekenpublic recordtanımınıpublic value recordolarak değiştirmek olacaktır. - JDK 25 / 26 / 27 Üzerinde AOT Önbellek Hattını Kurun:
JEP 544'ü (JDK 28) beklemek zorunda değilsiniz. Halihazırda JDK 25 LTS veya JDK 27 kullanıyorsanız, JEP 514, 515 ve 516 ile gelen-XX:AOTCacheOutputmekanizmasını Docker imaj üretim süreçlerinize entegre edin. JDK 28'e geçtiğinizde aynı komut yapısı hiçbir değişiklik gerektirmeden JEP 544'ün yerel makine kodu derleyicisini de otomatik olarak devreye sokacaktır. - Bellek Limitlerini Yeniden Ölçümleyin:
JDK 27'de varsayılan olarak açık gelen JEP 534 (Compact Object Headers) sayesinde konteyner bellek ayak izinizde anında düşüş göreceksiniz. Kubernetesresources.requests.memoryve-Xmxayarlarınızı yeni çalışma zamanı metriklerine göre yeniden kalibre ederek bulut altyapı maliyetlerinizi optimize edin.
Sonuç
Ron Pressler'ın vurguladığı gibi; yapay zeka ajanları kod yazma hızını demokratikleştirdikçe, mühendislik üstünlüğü dilin sözdizimsel şekerlemelerinden (syntactic sugar) çalışma zamanının (runtime) ham verimliliğine taşınıyor. Project Lilliput ile nesne başlıklarını daraltan, Project Valhalla (JEP 401 & 539) ile belleği CPU önbelleklerine düzleştiren ve Project Leyden (JEP 544) ile dinamik yeteneklerinden ödün vermeden AOT makine kodu hızına ulaşan JVM; önümüzdeki on yılın yüksek yoğunluklu, bulut-yerel ve ajanik sistemleri için en sağlam mühendislik kalesi olmaya devam ediyor.
Yazılım mimarisi, mikroservis ekosistemleri ve üretken yapay zeka uygulamaları üzerine çalışıyorum. TastutanLabs çatısı altında geliştirici araçları ve SaaS ürünleri inşa ediyorum.
Yorumlar (0)
Düşüncelerinizi Paylaşın
Henüz yorum yapılmamış. İlk yorumu siz yapın!
Bu Kategorideki Diğer İncelemeler
Project Loom Üçlemesi Tamamlanıyor: JEP 543 ile Structured Concurrency'nin Nihai Mimarisi, JEP 540 Yerleşik JSON API ve Kurumsal Mikroservislerde Eşzamanlılık Devrimi
Devoxx Belgium 2026 sahnesinde Alan Bateman ve Viktor Klang'ın paylaştığı mimari sırlar: JDK 27 (JEP 533) ile keskinleşen ve JDK 28 (JEP 543) ile kalıcı hale gelmeye hazırlanan StructuredTaskScope API'sinin derinlemesine analizi, Joiner stratejileri, yetim thread sızıntılarının sonu ve JEP 540 Simple JSON API ile Veri Odaklı Programlama (DOP) vizyonu.