---
title: 'Sınıf Yükleme'
source: 'https://academia.sh/tr/kurslar/jvm-ve-eszamanlilik/sinif-yukleme'
course: 'JVM, Eşzamanlılık ve Başarım'
language: tr
updated: '2026-08-23T07:00:56+00:00'
license: 'CC BY-SA 4.0'
---

# Sınıf Yükleme

Yükleyici hiyerarşisi yetki devriyle çalışır: bir yükleyiciye sorulan sınıfı çoğu zaman üstteki yükleyici tanımlar, kimlik yalnız `==` ile okunur. Dört tetikleyicinin üçü sınıf başlatmasını koşturur, biri (derleme zamanı sabiti okumak) hiç koşturmaz; aynı sınıfa aynı anda dokunan iki iş parçacığından yalnızca biri başlatma bloğunu çalıştırır, öbürü bitmesini bekler.

Standart Kütüphane ve Akışlar kursu son dersini bir garantiyle kapattı: bir davranışın
arkasında artık tek bir iş parçacığı durmuyordu, aynı nesneye aynı anda erişen birden çok
iş parçacığı vardı. Bu kurs o garantinin tam olarak ne olduğunu soracak, ama önce ondan
daha küçük ve yine de yalnız koda bakarak yanıtlanamayan bir soru var: bu satırı hangi iş
parçacığı koşturuyor? Soru, yazdığımız ilk satırdan önce başlıyor. `main` çalışmaya
başlamadan önce bile bir sınıfın çalışma zamanı biçimine girmesi gerekir — yüklenmesi ve
başlatılması — ve bu işi de bir iş parçacığı yapar: sınıfa ilk dokunan iş parçacığı.

Bu ders iki soruyu ayırır. Birincisi: bir sınıf istendiğinde onu hangi yükleyici tanımlar?
İkincisi: başlatma bloğunu hangi iş parçacığı koşturur, ve aynı sınıfa aynı anda dokunan
ikinci bir iş parçacığına ne olur?

## Yükleyici Hiyerarşisi ve Yetki Devri

Bir sınıf istendiğinde JVM onu tek bir yükleyiciye değil bir yükleyici zincirine sorar.
Zincirdeki her yükleyicinin bir üstü (parent) vardır; zincirin en tepesinde JVM'in kök
yükleyicisi durur ve bu yükleyici bir Java nesnesi olarak temsil edilmez — `getClassLoader()`
çağrısı onun için `null` döner. Bir sınıfı isteyen yükleyici (initiating loader) kendi işini
önce üstüne devreder: üstüne sorar, üst de kendi üstüne sorar. Zincirde bir yükleyici sınıfı
gerçekten tanımlayabilirse (`defineClass` çağrısını yapabilirse) o sınıfın tanımlayan
yükleyicisi (defining loader) odur. Bu yetki devri (delegation) ilkesidir ve
`ClassLoader.loadClass` yönteminin varsayılan davranışıdır — hiçbir satır yazmadan, yalnız
bir yükleyicinin üstünü belirterek devralınır.

Yetki devrinin sonucu isteyen ile tanımlayanı ayırabilir: bir yükleyiciye bir sınıf sorulur,
ama sınıfı üstteki bulur ve tanımlarsa isteyen ile tanımlayan aynı nesne değildir. Bu fark
çağrı yazımında görünmez — `alt.loadClass("X")` çağrısı her koşulda aynı satırdır; hangi
yükleyicinin tanımladığı yalnız dönen `Class` nesnesinin `getClassLoader()`'ı `==` ile
karşılaştırılarak görülür.

- **JV1** — Yükleyici kimliği yalnız `==` karşılaştırmasıyla okunur; yükleyicinin adı,
  kimlik karması ya da `toString` çıktısı hiçbir yerde basılmaz.
- **JV2** — Derleyici, standart kitaplığın araç sağlayıcı arayüzünden program içinden
  çağrılır; iki küçük sınıf iki ayrı dizine derlenir ve bu iki dizin dışında hiçbir kaynak
  ölçüme katılmaz.
- **JV3** — Üstteki yükleyicinin bu ölçümde kendi üstü yoktur; zincirin ne kadar
  yükseldiği değil, hangi katmanın tanımlayan olduğu ölçülür.

```java
// Delegeli.java — yetki devriyle calisan yukleyici: findClass override edilir, loadClass devralinir
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

class Delegeli extends ClassLoader {
    private final Path dizin;

    Delegeli(Path dizin, ClassLoader ust) {
        super(ust);
        this.dizin = dizin;
    }

    protected Class<?> findClass(String ad) throws ClassNotFoundException {
        try {
            byte[] bayt = Files.readAllBytes(dizin.resolve(ad + ".class"));
            return defineClass(ad, bayt, 0, bayt.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(ad);
        }
    }
}
```

`Delegeli` yalnız `findClass`'ı yeniden tanımlar; `loadClass`'a hiç dokunulmaz. `Paylasilan`
sınıfının bayt kodu yalnızca üstteki yükleyicinin baktığı dizinde durur, `Ozel`in bayt kodu
yalnızca alttakinin baktığı dizinde. Böylece iki dizin, iki ayrı yükleyicinin görebildiği iki
ayrı kaynağı temsil eder.

```java
// Yukleyiciler.java — isteyen yukleyici ile tanimlayan yukleyici kimlik karsilastirmasiyla ayrilir
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.file.*;
import java.util.spi.ToolProvider;

public class Yukleyiciler {
    static void derle(String kaynak, String sinif, Path dizin) throws Exception {
        Path d = Files.createTempDirectory("kaynak");
        Path k = d.resolve(sinif + ".java");
        Files.writeString(k, kaynak);
        Files.createDirectories(dizin);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        ToolProvider.findFirst("javac").orElseThrow()
                .run(bos, bos, "-d", dizin.toString(), k.toString());
    }

    public static void main(String[] args) throws Exception {
        Path ustDizin = Files.createTempDirectory("ust");
        Path altDizin = Files.createTempDirectory("alt");
        derle("class Paylasilan { static int deger() { return 1; } }", "Paylasilan", ustDizin);
        derle("class Ozel { static int deger() { return 2; } }", "Ozel", altDizin);

        ClassLoader ust = new Delegeli(ustDizin, null);
        ClassLoader alt = new Delegeli(altDizin, ust);

        Class<?> c1 = alt.loadClass("Paylasilan");
        Class<?> c2 = alt.loadClass("Ozel");

        System.out.println("Paylasilan sinifini tanimlayan : "
                + (c1.getClassLoader() == ust ? "ustteki" : c1.getClassLoader() == alt ? "kendisi" : "baska"));
        System.out.println("Ozel sinifini tanimlayan       : "
                + (c2.getClassLoader() == alt ? "kendisi" : c2.getClassLoader() == ust ? "ustteki" : "baska"));
        System.out.println("kok yukleyici temsili (null)   : " + (Object.class.getClassLoader() == null));
    }
}
```

```
Paylasilan sinifini tanimlayan : ustteki
Ozel sinifini tanimlayan       : kendisi
kok yukleyici temsili (null)   : true
```

Aynı `alt.loadClass(...)` çağrısı iki kez yapıldı ve iki ayrı yanıt geldi. `Paylasilan`
alt yükleyiciye soruldu, ama bulan üstteki yükleyiciydi — yetki devri üstte durdu ve
tanımlayan `alt` değil `ust` çıktı. `Ozel` üstteki yükleyicinin baktığı dizinde yoktu; devir
başarısız olunca istek alta geri döndü ve tanımlayan bu kez `alt`'ın kendisi oldu. Aradaki
tek fark, ikinci sınıfın bayt kodunun üstte hiç bulunmamasıydı — kodda hiçbir dallanma bunu
söylemiyor. `Object.class.getClassLoader()` çağrısı `null` verdi: zincirin en üstünde,
hiçbir Java nesnesiyle temsil edilmeyen kök yükleyici duruyor.

## Sınıf Başlatmasını Hangi İş Parçacığı Koşturuyor

Yükleme (bir sınıf dosyasının bulunup çalışma zamanı temsilinin kurulması) ile başlatma
(statik alanlara ilk değerlerin verilmesi ve başlatma bloğunun çalıştırılması) iki ayrı
olaydır ve iki ayrı anda olabilir. JVM bir sınıfı istendiği anda değil, gerçekten
kullanılacağı anda başlatır ve tam olarak bir kez başlatır. İki iş parçacığı aynı sınıfa
aynı anda dokunursa biri başlatma bloğunu koşturur, öbürü bitene kadar bekler — sıra
çizelgeleyiciye bırakılmaz, bu bir dil garantisidir ve JVM içinde örtük bir kilitle sağlanır.

Hangi eylemlerin bir sınıfı gerçekten başlattığı da çağrı yazımına bakarak görünmez: dört
aday eylemden üçü başlatmayı tetikler, biri hiç tetiklemez.

- **JV4** — Bir sınıfın başlatılıp başlatılmadığı kendi kurduğumuz sayaçla izlenir; sayaç
  yalnız başlatma bloğunun gövdesi gerçekten çalıştığında artar, sınıfın yüklenmesiyle
  artmaz.
- **JV5** — Dört tetikleyici dört ayrı sınıf üzerinde denenir, çünkü bir sınıf en çok bir
  kez başlar; aynı sınıfı iki tetikleyiciyle sınamak ikinci denemeyi anlamsız kılar.
- **JV6** — İki iş parçacığının aynı sınıfa aynı anda dokunması iki `CountDownLatch` ile
  kurgulanır: biri ikisinin de hazır olduğunu, öbürü ikisinin de aynı anda serbest
  bırakıldığını garanti eder; aradaki sıra çizelgeleyiciye bırakılmaz.
- **JV7** — Yarışan iki iş parçacığı da aynı etiketle ("oluşturulan") kayıtlıdır; hangisinin
  kazandığı bu ölçümün konusu değildir, kazananın kayıtta olup olmadığı konudur.

```java
// Bas.java — sinif baslatmasini hangi is parcacigi kosturuyor: kendi kaydimizla izlenir
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

class Bas {
    static final AtomicInteger sayac = new AtomicInteger(0);
    static final List<String> gorulen = Collections.synchronizedList(new ArrayList<>());
    static final Map<Thread, String> etiketler = Collections.synchronizedMap(new IdentityHashMap<>());

    static void tanit(Thread t, String etiket) { etiketler.put(t, etiket); }

    static String kim() {
        String e = etiketler.get(Thread.currentThread());
        return e == null ? "bilinmeyen" : e;
    }

    static void gozle(String tetikleyici) {
        sayac.incrementAndGet();
        gorulen.add(tetikleyici);
        System.out.printf("%-30s baslatici kosuyor: %s%n", tetikleyici, kim());
    }
}
```

Dört tetikleyici dört ayrı sınıfa yazılır; her sınıfın başlatma bloğu yalnız `Bas.gozle`'yi
çağırır.

```java
// YeniOrnekTetigi.java — yeni ornek olusturma baslatmayi tetikler mi
class YeniOrnekTetigi { static { Bas.gozle("yeni ornek olusturma"); } }
```

```java
// StatikYontemTetigi.java — statik yontem cagirma baslatmayi tetikler mi
class StatikYontemTetigi { static { Bas.gozle("statik yontem cagirma"); } static void yontem() { } }
```

```java
// StatikAlanTetigi.java — final olmayan statik alan okuma baslatmayi tetikler mi
class StatikAlanTetigi { static { Bas.gozle("statik alan okuma"); } static int deger = 5; }
```

```java
// SabitTetigi.java — derleme zamani sabiti okuma baslatmayi tetikler mi
class SabitTetigi { static { Bas.gozle("derleme zamani sabiti okuma"); } static final int SABIT = 7; }
```

Beşinci sınıf iki ölçümde birden kullanılır: önce yüklenip başlatılmadan durabildiğini, sonra
iki iş parçacığının aynı anda dokunduğunda başlatmanın tam bir kez çalıştığını gösterir.

```java
// GecBaslatilan.java — once yuklenip baslatilmadan durabilen, sonra iki iplik tarafindan es zamanli dokunulan sinif
class GecBaslatilan { static { Bas.gozle("gec baslatilan sinif"); } static int deger = 9; }
```

```java
// Tetikleyiciler.java — dort tetikleyicinin kaci baslatmayi kosturuyor, yukleme baslatmadan ayri mi, es zamanli dokunma ne veriyor
import java.util.concurrent.CountDownLatch;

public class Tetikleyiciler {
    public static void main(String[] args) throws Exception {
        Bas.tanit(Thread.currentThread(), "cagiran");

        System.out.println("-- dort tetikleyici --");
        new YeniOrnekTetigi();
        StatikYontemTetigi.yontem();
        int a = StatikAlanTetigi.deger;
        int s = SabitTetigi.SABIT;
        System.out.println("okunan sabit deger                      : " + s);
        System.out.println("yeni ornek olusturma baslatti mi         : " + Bas.gorulen.contains("yeni ornek olusturma"));
        System.out.println("statik yontem cagirma baslatti mi        : " + Bas.gorulen.contains("statik yontem cagirma"));
        System.out.println("statik alan okuma baslatti mi            : " + Bas.gorulen.contains("statik alan okuma"));
        System.out.println("derleme zamani sabiti okuma baslatti mi  : " + Bas.gorulen.contains("derleme zamani sabiti okuma"));
        System.out.println("dort tetikleyicinin kosturdugu baslatici : " + Bas.sayac.get());

        System.out.println("-- yukle, baslatma; sonra iki iplik es zamanli dokunsun --");
        ClassLoader yukleyici = Tetikleyiciler.class.getClassLoader();
        Class<?> yuklenen = Class.forName("GecBaslatilan", false, yukleyici);
        System.out.println("yalnizca yuklendi, baslatici kosan sayisi: " + Bas.sayac.get());

        CountDownLatch hazir = new CountDownLatch(2);
        CountDownLatch baslasin = new CountDownLatch(1);
        Runnable govde = () -> {
            hazir.countDown();
            try { baslasin.await(); } catch (InterruptedException e) { }
            int x = GecBaslatilan.deger;
        };
        Thread ilk = new Thread(govde);
        Thread ikinci = new Thread(govde);
        Bas.tanit(ilk, "olusturulan");
        Bas.tanit(ikinci, "olusturulan");
        ilk.start();
        ikinci.start();
        hazir.await();
        baslasin.countDown();
        ilk.join();
        ikinci.join();
        System.out.println("iki iplik dokundukten sonra Class ayni mi: " + (yuklenen == GecBaslatilan.class));
        System.out.println("baslatici toplam kac kez kostu           : " + Bas.sayac.get());
    }
}
```

```
-- dort tetikleyici --
yeni ornek olusturma           baslatici kosuyor: cagiran
statik yontem cagirma          baslatici kosuyor: cagiran
statik alan okuma              baslatici kosuyor: cagiran
okunan sabit deger                      : 7
yeni ornek olusturma baslatti mi         : true
statik yontem cagirma baslatti mi        : true
statik alan okuma baslatti mi            : true
derleme zamani sabiti okuma baslatti mi  : false
dort tetikleyicinin kosturdugu baslatici : 3
-- yukle, baslatma; sonra iki iplik es zamanli dokunsun --
yalnizca yuklendi, baslatici kosan sayisi: 3
gec baslatilan sinif           baslatici kosuyor: olusturulan
iki iplik dokundukten sonra Class ayni mi: true
baslatici toplam kac kez kostu           : 4
```

Dört adaydan üçü başlatmayı gerçekten koşturdu: yeni örnek oluşturmak, statik yöntem
çağırmak, final olmayan bir statik alanı okumak. Dördüncüsü — derleme zamanı sabitini
okumak — hiçbir satır bırakmadı; `SabitTetigi` sınıfının başlatma bloğu hiç çalışmadı, buna
karşılık okunan değer (`7`) doğru geldi. Bu satır aşağıda ayrı bir bölümde ele alınıyor.

İkinci blok yükleme ile başlatmayı ayırıyor. `Class.forName("GecBaslatilan", false, ...)`
sınıfı yükledi — bir `Class` nesnesi döndü — ama sayaç `3`'te kaldı: başlatma bloğu
çalışmadı. Yükleme, başlatmadan önce ve ondan bağımsız olarak tamamlanabiliyor.

Üçüncü blok aynı sınıfa iki iş parçacığını aynı anda gönderiyor. `hazir` mandalı ikisinin de
başlama çizgisinde olduğunu, `baslasin` mandalı ikisinin de aynı anda serbest bırakıldığını
garanti ediyor; hangisinin `GecBaslatilan.deger` okumasına önce ulaşacağı ise çizelgeleyiciye
bağlı ve bilinmiyor. Buna karşılık sonuç belirlenimci: başlatıcı **toplamda bir kez** koştu
(sayaç `3`'ten `4`'e çıktı, ikiye değil) ve koşturan taraf kayıtlı iki iş parçacığından
biriydi — `bilinmeyen` değil. İkinci iş parçacığı da `GecBaslatilan.deger`'i okudu, ama
kendi başlatma bloğunu çalıştırmadı; JVM'in örtük kilidine takılıp birincinin bitmesini
bekledi. Hangi iş parçacığının kazandığı koşumdan koşuma değişebilir, ama kazananın kayıtta
olduğu ve başlatmanın tam bir kez çalıştığı değişmiyor — bu yüzden ders iki iş parçacığını
da aynı etiketle kaydetti (JV7).

## Sınırlayıcı Ölçüm: Derleme Zamanı Sabiti Sınıfı Hiç Başlatmaz

Yukarıdaki tabloda dördüncü satır boştu: `SabitTetigi` sınıfının başlatma bloğu hiç çalışmadı
ve `Bas.gozle` hiç çağrılmadı. Nedeni `SABIT` alanının bildirim biçiminde: `static final int
SABIT = 7;` bir derleme zamanı sabitidir, ve derleyici böyle bir alanı okuyan her yeri
sabitin değeriyle değiştirir — Nesneye Dayalı Java kursunun ölçtüğü **değer gömme**. `int s =
SabitTetigi.SABIT;` satırı sınıf dosyasında `SabitTetigi`'ye ait hiçbir iz bırakmaz;
`7` değeri doğrudan çağrı yerine gömülür. Sınıfa hiç dokunulmadığı için başlatılacak bir şey
de yoktur.

Bu, ortak ölçüm çerçevesinin kendi kör noktasının bu derste yeniden kurulmuş hâlidir: boş bir
satır "başlatma olmadı" demek değildir, "tetikleyici hiç oluşmadı" demektir. Bir ölçüm yalnız
beklenen satırın yokluğuna bakarak "düzenek çalışmıyor" sonucuna varamaz; önce o satırı
üretecek eylemin gerçekten gerçekleştiğini doğrulamak zorundadır. Burada eylem (bir alanı
okumak) gerçekleşti, ama okunan şey sınıfa değil, derleyicinin sınıfın yerine koyduğu sayıya
gitti. Gözlemin yokluğu, düzeneğin yokluğu değildir.

`StatikAlanTetigi` ile `SabitTetigi` arasındaki tek fark bir `final` sözcüğüdür; ikisi de
`int` türünde bir alan tutar, ikisi de aynı tetikleyici satırla okunur. Buna karşılık biri
sınıfını başlatırken öbürü hiç dokunmaz — kararı veren şey alanın türü ya da değeri değil,
değerinin derleme zamanında bilinip bilinmediğidir. Bu ayrım, Java Temelleri kursunun ilk
dersinde ölçülen sabit katlamayla aynı kaynaktan gelir: derleyici, sonucu önceden
hesaplayabildiği her yerde çalışma zamanına hiçbir iz bırakmaz.

## Özet

- Yükleyici hiyerarşisi yetki devriyle çalışır: bir sınıfı isteyen yükleyici önce üstüne
  sorar, sınıfı üstteki bulursa tanımlayan üstteki olur; kimlik yalnız `==` ile okunur, ad
  ve sayı basılmaz.
- Kök yükleyici bir Java nesnesi olarak temsil edilmez; `getClassLoader()` onun için `null`
  döner.
- Yükleme ile başlatma iki ayrı olaydır: bir sınıf, başlatma bloğu hiç çalışmadan
  yüklenebilir.
- Dört tetikleyicinin üçü (yeni örnek, statik yöntem çağrısı, final olmayan statik alan
  okuma) sınıf başlatmasını koşturur; derleme zamanı sabitini okumak koşturmaz, çünkü değer
  çağrı yerine gömülür ve sınıfa hiç dokunulmaz.
- Aynı sınıfa aynı anda dokunan iki iş parçacığından yalnızca biri başlatma bloğunu çalıştırır;
  öbürü JVM'in örtük kilidine takılıp bitmesini bekler. Kazanan taraf koşumdan koşuma
  değişebilir, ama her zaman kayıtlı bir iş parçacığıdır ve başlatma toplamda bir kez koşar.
- Bir ölçümün beklediği satır çıktıda yoksa, önce o satırı üretecek eylemin gerçekten
  tetiklenip tetiklenmediği doğrulanır; boş satır "olmadı" değil "tetiklenmedi" anlamına
  gelebilir.

## Sonraki Adım

Bu derste sınıfın çalışma zamanına kimin elinde girdiği ölçüldü: hangi yükleyicinin
tanımladığı, hangi iş parçacığının başlattığı. Ama başlatılan sınıfın alanları nerede
durduğu, ve bir iş parçacığının yazdığı değeri başka bir iş parçacığının görüp göremediği
hiç sorulmadı — `GecBaslatilan.deger` okundu, ama kimin yazdığı değeri gördüğü hiç
sınanmadı. Sıradaki ders bunu ölçer: yerel değişken, statik alan, nesne alanı ve iş
parçacığına özel bir değer dört ayrı yerde durur, ve bunlardan yalnızca bir kısmı yazan iş
parçacığının dışına görünür.
