İçeriğe geç

Sınıf Yükleme ve JIT — Uygulama Neden Isınır?

Uzman 9 dk Sık karşılaşılır

Önce şunu oku: Polimorfizm ve Dinamik Bağlama — Kararı Kim Veriyor?

30 saniyede özet

JVM kodu önce satır satır okur, sık çalışanı zamanla hızlı makine koduna çevirir; tahmini yanlış çıkarsa geri döner. Yeni sürüm yayına alınınca ilk dakikalardaki yavaşlığın sebebi bu ısınmadır.

Her yeni sürümü yayına aldıktan sonra site ilk birkaç dakika ağırlaşıyor ve alarm çalıyor. Sonra kendiliğinden düzeliyor. Kodda değişen bir şey yok.

  1. Bayt: Her yeni sürümden sonra site ilk dakikalarda ağırlaşıyor, alarm çalıyor!

  2. Sen: Sonra ne oluyor?

  3. Bayt: Sonra kendiliğinden düzeliyor. Kodda da değişen bir şey yok!

  4. Bayt: JVM de yeni işe başlayan biri gibi: önce el kitabından okur, sonra hızlanır.

Sınıf ilk kullanımda yüklenir

JVM, uygulamadaki her sınıfı başlangıçta yüklemez. Bir sınıfı ilk kez gerçekten kullandığında bir class loaderBir sınıfın bytecode'unu bulup JVM'e yükleyen nesne. Sınıflar başlangıçta değil, ilk gerçek kullanımda yüklenir ve static başlatıcıları bir kez çalışır.Sözlükte gör → onu bulur, doğrular ve static başlatıcılarını çalıştırır.

Bu bir kez ve thread-safe olur. Ama static init bir exception fırlatırsa sınıf o JVM’de bozuk kalır: ilk denemede ExceptionInInitializerError, sonrakilerde NoClassDefFoundError.

Kafam karıştı, daha basit anlat

JVM bir sınıfı ancak ilk kez gerçekten ihtiyaç duyduğunda raftan indirir. İndirirken bir şey ters giderse, o sınıf program kapanana kadar bozuk kalır.

Hızlı kontrolOrta

Bir sınıfın static başlatıcısı ne zaman çalışır?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Yavaş başla, hızlan

Kod önce yorumlanır. Sık çağrılan metodu JIT derleyiciÇalışma sırasında sık çağrılan bytecode'u makine koduna çeviren derleyici. HotSpot'ta C1 hızlı derleyip profil toplar, C2 bu profile göre optimize eder.Sözlükte gör → derler: önce C1 hızlıca derler ve hangi tiplerin, hangi dalların geldiğini sayar. Profil yeterince dolunca C2, bu profile güvenerek agresif optimize eder.

C2, total() içindeki item.price() çağrısını hep Book gördüğü için Book.price() olarak inline etti. Sonra ilk kez bir Gift geliyor. Ne olur? Cevabı göster

Kod geçersiz sayılır. C2 bir tip kontrolü bırakmıştı; kontrol tutmayınca metot yorumlayıcıya döner, yeniden profil toplanır ve yeniden derlenir.

Yanıt süresinin şekli: yavaş başlar, hızlanır, bir sürprizde tökezler, yeniden hızlanır.
Adım adım oku
  1. Uygulama açılınca kod yorumlayıcıyla, yani el kitabından okunarak çalışır ve yavaştır.
  2. Sık çalışan metotlar önce C1 ile hızlıca, sonra toplanan profille C2 ile iyice derlenir.
  3. C2'nin varsaydığı tip dışında bir nesne, örneğin ilk Gift gelince derlenmiş kod bırakılır: deopt.
  4. JVM yeni bilgiyle kodu yeniden derler ve yanıt süresi tekrar düşer.

Buna deoptimizationJIT'in bir varsayıma (ör. 'buraya hep Book gelir') dayanarak ürettiği kodun, varsayım bozulunca geçersiz sayılıp yorumlayıcıya dönülmesi.Sözlükte gör → denir. Hata değildir; JIT’in spekülasyonunun bedelidir. Ama sık oluyorsa, ısınma boşa gidiyordur.

Kafam karıştı, daha basit anlat

JIT, “Just-In-Time”, yani tam zamanında derleme demek. JVM önce kodu yavaşça okur, sık kullanılan yerleri fark edince onları hızlı makine koduna çevirir.

Hızlı kontrolİleri

JIT bağlamında deoptimization ne zaman olur?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kendin gör

Deploy sonrası ilk dakikalar — JIT ne yapıyor?

Tohum 962484
  • yorumlayıcı
  • C1
  • C2
  • deoptimization

Oynat ya da adımla.

Hız
Adım 0

Şu an ne oldu?

Yeni sürüm deploy edildi; total() her istekte çağrılıyor

JVM kodu önce yorumlar, sonra sık çağrılanı derler. Kullanıcılar bu sürecin hangi kısmını yaşıyor?

Görevler0/3

  • Gerçek kullanıcılar yorumlayıcıda çalışan kodla karşılansınaçık

    İpucu

    Isınma olmadan trafiğe açıl.

  • Isındığı hâlde canlı trafikte deoptimization yaşaaçık

    İpucu

    Isınma, gerçek trafiğin bir kısmını hiç görmesin.

  • Yeni tip gelse de canlı trafiğin tamamı C2 ile karşılansınaçık

    İpucu

    Isınma gerçek trafiğe benzesin.

Olay günlüğü (0)

Henüz olay yok. Oynat veya adımla.

  1. Varsayılanla oynat. Isınma yok: ilk canlı batch yorumlayıcıda, sınıf yükleme de kullanıcının üstünde.
  2. Isınmayı “sentetik” yap. Canlı trafik C2 ile başlıyor, ama Gift gelince deoptimization.
  3. Isınmayı “temsili” yap. Canlı trafiğin tamamı C2.
  4. Trafiği “yalnızca Book” yap. Sentetik ısınma artık yetiyor; çünkü trafik ona benziyor.
Hızlı kontrolİleri

Simülatörde sentetik (yalnızca Book) ısınmadan sonra canlı trafik C2 ile başladı, ama Gift gelince deoptimization oldu. Buradan çıkan ders ne?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Trafikten önce ısın

Deploy sonrası yavaşlığın kaynakları birden fazladır: sınıf yükleme, yorumlayıcı, boş bağlantı havuzu, soğuk cache. JVM tarafını azaltmanın yolları:

  • Temsili ısınma: gerçek trafiğe benzeyen istekler, readiness probe’u açılmadan önce.
  • Kademeli trafik: yeni instance’a yükü yavaş yavaş vermek.
  • Başlangıç arşivleri: CDS ve JDK 24 ile gelen Project Leyden AOT cache’i, sınıf yükleme işinin bir kısmını önceki çalıştırmadan getirir.
Kafam karıştı, daha basit anlat

Yeni açılan uygulama soğuk bir motor gibidir. Gerçek trafik gelmeden önce benzer isteklerle ısıtırsan, ilk kullanıcılar yavaşlığı hissetmez.

Hızlı kontrolİleri

Kubernetes'te yeni pod'lar trafiğe girer girmez birkaç dakika yüksek gecikme gösteriyor. Hangisi JVM ısınmasını kullanıcıdan uzak tutmanın en doğrudan yolu?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Tuzaklar

Elle yazılmış benchmark. Isınmayan, sonucu kullanılmayan bir System.nanoTime() döngüsü ya yorumlayıcıyı ölçer ya da JIT’in sildiği boş bir döngüyü. JMH kullan.

Static init’te I/O. Dosya ya da ağ hatası sınıfı kalıcı olarak bozar. Pahalı ve hata verebilen işi açık bir başlatma metoduna taşı.

Tek tipe göre ısınma. Sentetik ısınma yalnızca mutlu yolu çağırıyorsa, gerçek trafikteki ilk farklı istek deoptimization getirir.

Hızlı kontrolİleri

Log'da önce bir `ExceptionInInitializerError`, sonraki her istekte `NoClassDefFoundError: Could not initialize class TaxRules` görüyorsun. Sınıf dosyası jar'da duruyor. Ne oldu?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Bu 'benchmark', iki uygulamayı karşılaştırmak için kullanılıyor. En ciddi sorunun olduğu satır hangisi?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Hatalı satıra dokun, sonra kontrol et.

QuickBench.java
Java 21UTF-8LF

Aşağıdaki örnek bir bankanın hesap ekstresi servisinden: class loading bir format eklenti sistemiyle, JIT de doğru ölçüm düzeneğiyle gösteriliyor. ServiceLoader ile keşfedilen formatlar, tembel statik başlatma ve ısınmayı hesaba katan bir JMH benchmark’ı.

Derinleş · Hesap ekstresi formatları: class loading ve JIT 6 dosya · ~90 satır · ilk okumada atlayabilirsin
Proje dosyaları

src/main/java/statement/ StatementFormat.java Eklenti sözleşmesi: bir ekstre formatı. Kurumsal müşteriler CSV, MT940 ya da ISO 20022 camt.053 ister.

src/main/java/statement/StatementFormat.java
public interface StatementFormat {
String name(); // "csv", "mt940", "camt053"
byte[] export(List<Movement> movements);
}

src/main/java/statement/csv/ CsvFormat.java Bir eklenti ve onu ServiceLoader'a tanıtan dosya.

src/main/java/statement/csv/CsvFormat.java
public class CsvFormat implements StatementFormat {
@Override
public String name() {
return "csv";
}
@Override
public byte[] export(List<Movement> movements) {
String body = movements.stream()
.map(m -> m.valueDate() + ";" + m.amount().toPlainString() + ";" + m.balanceAfter().toPlainString())
.collect(Collectors.joining("\n", "valor;tutar;bakiye\n", "\n"));
return body.getBytes(StandardCharsets.UTF_8);
}
}
// File: src/main/resources/META-INF/services/statement.StatementFormat
// Content (one implementation per line):
// statement.csv.CsvFormat
// An MT940 or camt.053 module adds its own line in its own jar: no change here.

src/main/java/statement/ Formats.java Keşif: sınıflar ancak istendiğinde yüklenir ve başlatılır.

src/main/java/statement/Formats.java
public class Formats {
// ServiceLoader reads META-INF/services files on the classpath. Each class is
// loaded and instantiated only when the iteration reaches it.
public static Map<String, StatementFormat> discover() {
Map<String, StatementFormat> formats = new TreeMap<>();
for (StatementFormat format : ServiceLoader.load(StatementFormat.class)) {
formats.put(format.name(), format);
}
return formats;
}
}

src/main/java/statement/ InitOrder.java Statik başlatma sırası: sınıf ilk kullanımda başlatılır, bir kez.

src/main/java/statement/InitOrder.java
public class InitOrder {
// Loads the whole SWIFT BIC directory in its static initializer: slow, so do it only when needed.
static class BicDirectory {
static {
System.out.println("BicDirectory initialized"); // runs on first active use, once
}
static void lookup() {}
}
public static void main(String[] args) {
System.out.println("main started");
BicDirectory[] array = new BicDirectory[10]; // does NOT initialize the class
System.out.println("array created");
BicDirectory.lookup(); // this does
BicDirectory.lookup(); // and not again
// main started
// array created
// BicDirectory initialized
}
}

src/jmh/java/statement/ CsvExportBenchmark.java JMH: ısınma turları JIT'e derleme fırsatı verir; ölçüm ondan sonra başlar.

src/jmh/java/statement/CsvExportBenchmark.java
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 5, time = 1) // interpreted -> C1 -> C2 happens here, not in the results
@Measurement(iterations = 5, time = 1)
@Fork(2) // fresh JVMs: one lucky or unlucky run does not decide
@State(Scope.Benchmark)
public class CsvExportBenchmark {
List<Movement> movements;
CsvFormat format;
@Setup
public void setUp() {
movements = Movements.sample(1_000); // one busy month of a corporate account
format = new CsvFormat();
}
@Benchmark
public byte[] export() {
return format.export(movements); // returned, so the JIT cannot remove the work as dead code
}
}

ops/ jit-flags.sh JIT'in ne yaptığını görmek için bayraklar.

ops/jit-flags.sh
# Which methods get compiled, at which tier, and when (noisy: for experiments only)
java -XX:+PrintCompilation -jar app.jar | grep CsvFormat
# Inlining decisions of the C2 compiler
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining -jar app.jar
# Run the benchmark; read the error column, not just the score
./gradlew jmh

Kendini sına

Şimşek turu1/5

JVM bütün sınıfları uygulama açılırken bir kerede yükler.

Soru 1/3İleri

Program ne yazdırır?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.
Init.java
1class Config {
2 static { System.out.println("Config başlatıldı"); }
3 static final String NAME = "app";
4 static int port() { return 8080; }
5}
6
7System.out.println("başla");
8System.out.println(Config.NAME);
9System.out.println(Config.port());
Java 21UTF-8LF

Aklında kalacak üç şey

  1. 1 Sınıf program başlarken değil, ilk kullanıldığında yüklenir ve static kısmı bir kez çalışır. Bu adım hata verirse sınıf o JVM'de bir daha kullanılamaz.
  2. 2 JIT, o ana kadar gördüğüne güvenerek optimize eder. Beklenmedik bir tip ya da yol gelince optimizasyon geri alınır ve metot yeniden ısınır.
  3. 3 Isınma gerçek trafiğe benzemeli ve uygulama trafiğe açılmadan bitmeli. Hız iddiası JMH ya da gerçek yük testiyle ölçülür.
Sonraki kapı Döngüyle ölçtün ve kodun inanılmaz hızlı çıktı. Gerçekten hızlı mı, yoksa hiç çalışmadı mı? JMH ile Doğru Ölçüm — Sayıya Güvenmeden Önce · 9 dk

4 kart sonraki derste seni bekliyor

0/5 kart bu dersten toplandı