• 7.09.2026 21:01:47
  • Admin Admin

Spring Boot eğitimi kapsamında çoğu zaman atlanan metaspace analizi; JFR, NMT, heap dump ve classloader istatistikleriyle gerçek sızıntıyı geçici class yükleme artışından ayırmayı gerektirir.

Spring Boot'ta Java Microservices için Metaspace Sızıntısı Avı

Spring Boot eğitimi: Metaspace artışını ölçülebilir bir olaya dönüştürmek

Metaspace, Java heap'in dışında sınıf metadatasını tutar; bu nedenle yalnızca heap kullanım grafiğine bakmak yanıltıcıdır. Bir Java backend geliştirme servisine önce şu JVM bayraklarını ekleyin: Native Memory Tracking (NMT) süreç başlangıcında açılır, sonradan etkinleştirilemez. Prometheus tarafında `jvm_memory_used_bytes{area="nonheap",id="Metaspace"}` metriğini istek hızı ve pod yeniden başlatma sayısıyla aynı grafikte inceleyin. Sabit trafik altında metaspace'in her Full GC sonrasında daha yüksek bir tabanda kalması, araştırmaya değer sinyaldir.

-XX:NativeMemoryTracking=summary
-XX:+UnlockDiagnosticVMOptions
-Xlog:gc*,class+load=info,class+unload=info:file=/var/log/jvm/classes.log:time,level,tags
-XX:StartFlightRecording=name=metaspace,settings=profile,maxage=30m,maxsize=256m,dumponexit=true,filename=/var/log/jvm/metaspace.jfr

Önce-sonra karşılaştırmasını aynı canary pod üzerinde yapın: 15 dakika normal trafik, 15 dakika şüpheli endpoint'e sabit RPS ile yük uygulayın ve iki pencerede `jcmd VM.native_memory summary` çıktısını kaydedin. `Class` satırındaki committed alanı büyürken JFR'de `jdk.ClassLoad` sayısı artıyor ve `jdk.ClassUnload` sayısı sıfıra yakın kalıyorsa sorun GC eşik ayarı değildir. Sınıf boşaltımı, yalnızca sınıfı yükleyen classloader erişilemez olduğunda gerçekleşir; yaşayan tek bir static referans bile o loader altındaki tüm sınıfları tutar.

Java programlama eğitimi için JFR, NMT ve heap dump ile classloader kanıtı

JDK Mission Control ile `.jfr` dosyasında `Class Loading` görünümünü açın; sınıf adı öneki, yükleme zamanı ve classloader alanına göre gruplayın. Aynı anda NMT farkını alın. `VM.native_memory summary.diff` yalnızca önce `baseline` alındığında anlamlıdır; iki ayrı podun çıktısını diff etmek, farklı CDS ve framework başlangıç maliyetleri nedeniyle güvenilir değildir.

jcmd $PID VM.native_memory baseline
# Sabit RPS altinda 10 dakika bekleyin
jcmd $PID VM.native_memory summary.diff scale=MB
jcmd $PID VM.classloader_stats
jcmd $PID JFR.dump name=metaspace filename=/tmp/metaspace-after.jfr
jcmd $PID GC.heap_dump /tmp/service-after.hprof

Eclipse Memory Analyzer (MAT) ile HPROF dosyasında `Class Loader Explorer` raporunu çalıştırın ve bir uygulama classloader'ının retained set'ini inceleyin. Heap dump metaspace byte'larını doğrudan göstermez, fakat metaspace'i boşaltamayan `ClassLoader` nesnesine giden GC root zincirini gösterir. Sık görülen zincir `static Map -> listener/proxy -> bean -> application ClassLoader` biçimindedir. `GC.class_histogram` hızlı bir ilk bakış sağlar, ancak classloader sahipliğini göstermediği için histogramı tek başına sızıntı kanıtı kabul etmeyin.

Spring Framework ve Hibernate ORM kaynaklı classloader tutulumları

Spring Framework uygulamasında asıl hata çoğu zaman CGLIB proxy'nin kendisi değil, yeniden oluşturulan `ApplicationContext` veya eklenti classloader'ının global bir kayda kaçmasıdır. Aşağıdaki örnekte bir plugin context'i kapatılsa bile static registry içindeki listener, bean'i ve bean'in classloader'ını canlı tutar. Bu hata özellikle testlerde veya tenant başına dinamik context kuran Java microservices tasarımlarında görünür.

public final class GlobalListeners {
    private static final List<ApplicationListener<?>> LISTENERS = new CopyOnWriteArrayList<>();

    public static void add(ApplicationListener<?> listener) {
        LISTENERS.add(listener);
    }

    public static void remove(ApplicationListener<?> listener) {
        LISTENERS.remove(listener);
    }
}

@Component
final class PluginListener implements ApplicationListener<ContextClosedEvent> {
    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        GlobalListeners.remove(this);
    }
}

Düzeltmede `@PreDestroy` veya `ContextClosedEvent` ile kaydı kaldırmak gerekir; `WeakReference` eklemek tek başına güvenli değildir, çünkü listener'ın çalışma sırasında beklenmedik biçimde toplanmasına yol açabilir. Hibernate ORM tarafında her istek için `SessionFactory`, `EntityManagerFactory` ya da ayrı `ClassLoader` oluşturmayın. Bunlar metadata, entity persister ve Byte Buddy tarafından üretilen sınıflar üretir. Spring Data JPA'da tenant ayrımı için factory oluşturmak yerine uygun olduğunda Hibernate multi-tenancy, `CurrentTenantIdentifierResolver` ve tek `EntityManagerFactory` kullanın. Bu yaklaşım üretilen metadata grafiğini tenant sayısıyla çarpmayı önler.

Spring AI, Spring MCP ve Model Context Protocol entegrasyonlarında yaşam döngüsü

Spring AI veya Spring MCP ile Model Context Protocol araçlarını entegre ederken her sohbet isteğinde yeni Spring context'i, yeni HTTP client factory'si ya da runtime derlenmiş tool adapter'ı üretmeyin. Tool tanımlarını uygulama başlangıcında immutable bir registry'ye alın; istek bağlamını ise tool nesnesine alan olarak yazmak yerine metod parametresi veya Reactor `Context` ile taşıyın. Böylece kullanıcıya ait referanslar singleton tool registry üzerinden tutulmaz ve tool sayısı ile yüklü sınıf sayısı arasında kontrolsüz ilişki kurulmaz.

@Configuration
class ToolConfiguration {
    @Bean
    List<ToolCallback> toolCallbacks(CustomerTools tools) {
        return List.of(ToolCallback.from(tools));
    }
}

@Component
class CustomerTools {
    @Tool(description = "Reads a customer by id")
    CustomerView findCustomer(String customerId) {
        return loadCustomer(customerId);
    }
}

Bu bean'i request scope yapmayın. Request scope proxy'si normalde tek başına metaspace sızıntısı yaratmaz, fakat tool callback'ini static bir cache'e koyup proxy üzerinden request bean'e erişmek, request context kapanınca da referans zincirini sürdürebilir. Spring AI ve spring mcp kullanan servislerde JFR'de tool kaydı sırasında oluşan sınıf yüklemelerini deployment başlangıcındaki yüklemelerden ayırın; normal istek akışında `jdk.ClassLoad` oranının sürekli sıfıra yakın olması hedeflenebilir bir kabul kriteridir.

Java kursu projelerinde kalıcı koruma: metrik, alarm ve regresyon eşiği

Micrometer ile metaspace ve loaded-class sayısını aynı registry'ye verin. `ClassLoadingMXBean` toplam yüklenen sınıf sayısını monotonik artırdığı için tek başına alarm metriği değildir; `getLoadedClassCount()` anlık değerini ve metaspace committed değerini birlikte kaydedin. Bir java eğitimi ya da java fullstack eğitimi projesinde bu iki metriği load test raporuna eklemek, local IDE'de görünmeyen yeniden yükleme hatalarını CI ortamına taşır.

@Component
class JvmClassMetrics implements MeterBinder {
    @Override
    public void bindTo(MeterRegistry registry) {
        ClassLoadingMXBean classes = ManagementFactory.getClassLoadingMXBean();
        MemoryPoolMXBean metaspace = ManagementFactory.getMemoryPoolMXBeans().stream()
            .filter(p -> p.getName().equals("Metaspace"))
            .findFirst().orElseThrow();

        Gauge.builder("app_jvm_loaded_classes", classes, ClassLoadingMXBean::getLoadedClassCount)
            .register(registry);
        Gauge.builder("app_jvm_metaspace_committed_bytes", metaspace,
            p -> (double) p.getUsage().getCommitted()).register(registry);
    }
}

Gatling veya k6 ile sabit 100 RPS'te 20 dakikalık test çalıştırın. İlk 5 dakikayı warm-up sayın; kalan 15 dakikada `app_jvm_metaspace_committed_bytes` eğimini hesaplayın. Düzeltmeden önce örneğin her 5 dakikada yeni bir plugin context'i açılıyorsa sınıf sayısı basamaklı artar; düzeltmeden sonra context kapatmalarından iki Full GC döngüsü sonra anlık loaded class sayısı başlangıç bandına dönmelidir. Bu eşiği deploy sonrası canary alarmına koyun. Bu disiplin, java programlama eğitimi içinde anlatılan GC bilgisini üretimde doğrulanabilir bir kontrol haline getirir.

Sık Sorulan Sorular

Spring Boot eğitimi sırasında metaspace ile heap sızıntısını nasıl ayırırım?

Heap dump'ı MAT ile retained object zincirleri için, JFR `jdk.ClassLoad` ve `jdk.ClassUnload` olaylarını sınıf yaşam döngüsü için birlikte kullanın. NMT `Class` committed büyür, heap kullanımınız Full GC sonrası düşer ve unload olayı gelmezse classloader tutulumunu araştırın. `-XX:NativeMemoryTracking=summary` bayrağı süreç açılışında verilmelidir.

Spring Data JPA ve Hibernate ORM metaspace kullanımını neden artırır?

Hibernate ORM entity metadata, proxy ve bytecode enhancement yapıları oluşturur. Tek bir `EntityManagerFactory` beklenen başlangıç maliyetini yaratabilir; asıl risk her tenant, test veya istek için yeni factory ya da context oluşturmaktır. `jcmd VM.classloader_stats` ile loader sayısını, JFR ile yüklenen sınıf öneklerini ölçerek bu farkı doğrulayın.

Spring AI ve Spring MCP kullanan Java microservices servisinde class yükleme alarmı nasıl kurulur?

Micrometer üzerinden anlık loaded class sayısını ve Metaspace committed byte değerini yayınlayın. Sabit RPS altında warm-up sonrası sınıf yükleme hızı sıfıra yaklaşmalı, tool çağrıları yeni Spring context veya dinamik adapter sınıfı üretmemelidir. Alarmı yalnızca toplam yüklenen sınıf sayısına değil, 10 dakikalık metaspace eğimine ve pod yeniden başlatma oranına bağlayın.

Java backend geliştirme için JFR kaydını üretimde sürekli açık tutmak güvenli mi?

Sınırlı boyut ve süreyle ring-buffer yaklaşımı kullanın: `maxage=30m,maxsize=256m` gibi sınırlar disk büyümesini önler. Önce staging'de JFR açık ve kapalı koşullarda p99 gecikme, CPU ve disk I/O karşılaştırması yapın. Olay bazlı derin inceleme gerektiğinde aynı kaydı `jcmd JFR.dump` ile dışarı alın; heap dump alma işlemini ise disk alanı ve kısa süreli stop-the-world etkisi nedeniyle ayrı olay prosedürüne bağlayın.

AI / LLM Discovery

Bu makale Opendart Akademi Java eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt