---
title: 'Kaynak, Bayt Kodu ve Sanal Makine'
source: 'https://academia.sh/tr/kurslar/java-temelleri/kaynak-bayt-kodu-ve-sanal-makine'
course: 'Java Temelleri'
language: tr
updated: '2026-08-17T18:09:39+00:00'
license: 'CC BY-SA 4.0'
---

# Kaynak, Bayt Kodu ve Sanal Makine

Java'da yazılan kaynak, çalıştırılan şey değildir: on iki yöntemin sekizi sınıf dosyasında kaynakta bulunmayan bir adım taşır ve toplam 13 eklenen adım sayılır; buna karşılık kaynakta duran bir birleştirme sınıf dosyasında hiç yoktur.

Python müfredatı beş kursla kapandı ve kapanış kursu bir projenin sayısını tek soruyla
tanımladı: aynı girdi kaç ayrı sonuç veriyor. O müfredat boyunca ölçülen şeylerin ortak bir
yanı vardı — yazılan kaynak, yorumlayıcının gördüğü şeye yakın duruyordu. Bir sözdizim
biçiminin altında hangi yöntemin çağrıldığı sorulduğunda yanıt **çalışma zamanında**
aranıyordu, çünkü kaynak metin ile çalışan şey arasındaki mesafe kısaydı.

Java'da o mesafe kısa değildir. Kaynak ile çalıştırılan şeyin arasına bir **derleyici
(compiler)** ve onun ürettiği bir **sınıf dosyası (class file)** girer; **Java sanal makinesi
(Java virtual machine)** kaynağı değil o dosyayı okur. Aradaki fark bir biçim farkı değildir:
sınıf dosyasında kaynakta yazılmamış şeyler bulunur. Bu dersin ve bütün kursun sorusu buradan
çıkar — yazılan satır, sınıf dosyasında **kaç adıma ve hangi adımlara** dönüşüyor?

## Zincir ve Bu Dersin Payı

Bilgisayarlar Nasıl Çalışır kursu derleyici, yorumlayıcı ve sanal makine üçlüsünü, **bayt
kodu (bytecode)** kavramını ve **anında derlemeyi (just-in-time compilation)** kurdu; derleme
aşamalarını da ayrı bir derste anlattı. **Bunlar burada tekrarlanmaz.** Orada kurulan
**kavramdı**; burada ölçülen, bir derleyicinin bu kaynak için gerçekten ürettiği
**dosyadır**.

Zincir üç durak taşır. Kaynak metin insanın yazdığı şeydir. Derleyici onu bir sınıf dosyasına
çevirir; bu dosya bayt kodu taşır, yani sanal makinenin komut kümesiyle yazılmış bir komut
dizisi. Sanal makine dosyayı yükler ve komutları yürütür. İki geçişten yalnız birincisi bu
dersin konusudur: **kaynak ile sınıf dosyası arasındaki fark.**

Farkın ölçülebilir olması bir kolaylıktan gelir. Sınıf dosyasının biçimi tanımlıdır ve
standart kitaplık hem o dosyayı okuyan bir arayüz hem de program içinden çağrılabilen bir
derleyici taşır. Ölçüm için hiçbir dış araca gerek yoktur: bir metin veririz, derleriz, çıkan
dosyayı okuruz. Kaynağı kendimiz yazdığımız için ne yazdığımızı biliriz; dosyada bulduğumuz
fazlalık derleyicinin koyduğudur.

## Ölçüm Çekirdeği

Kursun bütün ölçümleri tek bir yardımcı sınıftan geçer. `Olcek`, verilen kaynak metnini
derler, ürettiği sınıf dosyasını okur ve her yöntem için iki şey döndürür: **kaç komut
(instruction)** üretildiği ve **kaynakta yazılmayan hangi adımın** eklendiği. Eklenen adım
beş türden biridir — bir çağrı, bir dinamik çağrı, bir nesne ya da dizi üretimi, bir tip
denetimi, bir sayısal çevirme.

Ölçümün varsayımları:

- **CZ1** — Kâhin kurgunun kendisidir: kaynağı biz yazdığımız için ne yazdığımızı biliriz,
  dolayısıyla sınıf dosyasında bulunan fazlalık bir çıkarım değil bir dökümdür.
- **CZ2** — Derleyici ve sınıf dosyası okuyucusu standart kitaplığın parçasıdır ve program
  içinden çağrılır; hiçbir dış araç kullanılmaz.
- **CZ3** — Sayılan birim **komuttur**. Sayılar bir derleyici koşumundan gelir ve o koşumda
  belirlenimcidir; dersin tezi tek bir komut sayısına değil, sayılar arasındaki **yöne**
  dayanır.
- **CZ4** — Ölçüm hiçbir ortama bağlı veri okumaz: süre, bellek adresi, kimlik ve yol
  yazılmaz.
- **CZ5** — Ölçülen kaynak on iki yöntem taşır ve bu yöntemler kursun ders ders ödeyeceği
  sözdizim biçimlerini kapsar; dilin bütün sözdizimi değildir.

```java
// Olcek.java — ölçüm çekirdeği: derleyicinin sınıf dosyasına ne eklediğini okur
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.classfile.instruction.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Olcek {
    record Yontem(String ad, int komut, List<String> eklenen) {}

    static ClassModel derle(String kaynak, String sinif) throws Exception {
        Path d = Files.createTempDirectory("olcek");
        Path k = d.resolve(sinif + ".java");
        Files.writeString(k, kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(bos, bos, "-d", d.toString(), k.toString()) != 0)
            throw new IllegalStateException("derlenmedi: " + sinif);
        return ClassFile.of().parse(d.resolve(sinif + ".class"));
    }

    static List<Yontem> oku(String kaynak, String sinif) throws Exception {
        List<Yontem> yontemler = new ArrayList<>();
        for (MethodModel m : derle(kaynak, sinif).methods()) {
            if (m.code().isEmpty()) continue;
            List<String> eklenen = new ArrayList<>();
            int komut = 0;
            for (CodeElement e : m.code().get()) {
                if (e instanceof Instruction) komut++;
                if (e instanceof InvokeInstruction iv)
                    eklenen.add(kisa(iv.owner().asInternalName()) + "." + iv.name().stringValue());
                else if (e instanceof InvokeDynamicInstruction id)
                    eklenen.add("dinamik:" + id.name().stringValue());
                else if (e instanceof NewObjectInstruction n)
                    eklenen.add("yeni " + kisa(n.className().asInternalName()));
                else if (e instanceof NewPrimitiveArrayInstruction a)
                    eklenen.add("yeni dizi " + a.typeKind().name().toLowerCase(Locale.ROOT));
                else if (e instanceof NewReferenceArrayInstruction a)
                    eklenen.add("yeni dizi " + kisa(a.componentType().asInternalName()));
                else if (e instanceof TypeCheckInstruction t && t.opcode() == Opcode.CHECKCAST)
                    eklenen.add("tip denetimi " + kisa(t.type().asInternalName()));
                else if (e instanceof ConvertInstruction c)
                    eklenen.add("cevirme " + c.opcode().name().toLowerCase(Locale.ROOT));
            }
            yontemler.add(new Yontem(m.methodName().stringValue(), komut, eklenen));
        }
        return yontemler;
    }

    static String kisa(String ic) { return ic.substring(ic.lastIndexOf('/') + 1); }
}
```

Ölçülen kaynak da ayrı bir dosyada durur, çünkü bu derste iki kez okunacaktır.

```java
// Kaynak.java — ölçülen kaynak metni: on iki yöntem, on iki sözdizim biçimi
class Kaynak {
    static final String ORNEK = """
        import java.util.List;
        class Ornek {
            static int topla(int a, int b) { return a + b; }
            static Object kutula(int n) { Integer k = n; return k; }
            static int kutuAc(Integer k) { int n = k; return n; }
            static String birlestir(String a, int n) { return a + n; }
            static String sabitBirlestir() { return "ba" + "sit"; }
            static String donguyleBirlestir(String[] d) {
                String s = "";
                for (String x : d) s += x;
                return s;
            }
            static int diziToplami(int[] d) { int t = 0; for (int x : d) t += x; return t; }
            static int listeToplami(List<Integer> l) { int t = 0; for (int x : l) t += x; return t; }
            static long yukselt(byte b, long u) { return b + u; }
            static byte daralt(byte b) { b += 300; return b; }
            static int degisken(int... sayilar) { return sayilar.length; }
            static int degiskenCagrisi() { return degisken(1, 2, 3); }
        }
        """;
}
```

## Yöntem Yöntem: Kaç Komut, Hangi Adım

```java
// Ana.java — her yöntem için komut sayısı ve kaynakta yazılmayan adım
public class Ana {
    public static void main(String[] args) throws Exception {
        System.out.printf("%-18s %6s  %s%n", "yontem", "komut", "kaynakta yazilmayan adim");
        int yazilan = 0, eklenenli = 0, toplam = 0;
        for (Olcek.Yontem y : Olcek.oku(Kaynak.ORNEK, "Ornek")) {
            System.out.printf("%-18s %6d  %s%n", y.ad(), y.komut(),
                    y.eklenen().isEmpty() ? "-" : String.join(", ", y.eklenen()));
            if (y.ad().equals("<init>")) continue;
            yazilan++;
            if (!y.eklenen().isEmpty()) eklenenli++;
            toplam += y.eklenen().size();
        }
        System.out.println("yazilan yontem: " + yazilan
                + " | eklenen adim tasiyan: " + eklenenli
                + " | toplam eklenen adim: " + toplam);
    }
}
```

```
yontem              komut  kaynakta yazilmayan adim
<init>                  3  Object.<init>
topla                   4  -
kutula                  5  Integer.valueOf
kutuAc                  5  Integer.intValue
birlestir               4  dinamik:makeConcatWithConstants
sabitBirlestir          2  -
donguyleBirlestir      24  dinamik:makeConcatWithConstants
diziToplami            24  -
listeToplami           20  List.iterator, Iterator.hasNext, Iterator.next, tip denetimi Integer, Integer.intValue
yukselt                 5  cevirme i2l
daralt                  7  cevirme i2b
degisken                3  -
degiskenCagrisi        16  yeni dizi int, Ornek.degisken
yazilan yontem: 12 | eklenen adim tasiyan: 8 | toplam eklenen adim: 13
```

## Sayıların Okunması

Yazılan on iki yöntemin **sekizi** sınıf dosyasında kaynakta bulunmayan bir adım taşıyor;
toplam **13 eklenen adım**. Kursun taşıyacağı ölçü budur: **eklenen adım kuraldır, istisna
değildir.** Tablonun her satırında solda yazdığımız yöntem, sağda derleyicinin oraya koyduğu
şey duruyor.

Satırlar tek tek okunduğunda eklemenin nereden geldiği görünür. `Integer k = n` kaynakta bir
atamadır, sınıf dosyasında bir `Integer.valueOf` çağrısıdır. `a + n` bir işleç gibi yazılır,
sınıf dosyasında bir **dinamik çağrıdır**. `b + u` satırında `byte` değerinin `long` ile
toplanabilmesi için bir çevirme komutu eklenmiştir. `degisken(1, 2, 3)` üç argümanlı bir
çağrı gibi durur; sınıf dosyasında önce bir dizi üretilir, sonra tek argümanlı bir çağrı
yapılır — bu yüzden üç komutluk yöntemin çağrısı **16 komut** tutar.

Eklenen adım taşımayan **dört** yöntem de aynı ölçüde bilgi verir: `topla`, `sabitBirlestir`,
`diziToplami` ve `degisken`. Ortak yanları şudur — hiçbiri çalışma zamanında bir başkasına
soru sormaz. İki tam sayıyı toplamak, bir dizinin öğesini okumak, bir dizinin uzunluğunu
sormak: bunların hepsi sanal makinenin doğrudan komutlarıyla karşılanır ve araya bir çağrı
girmez. Eklenen adımın koşulu buradan okunur: derleyici, yazılan biçimin karşılığını komut
kümesinde **bulamadığında** araya bir adım koyar.

Bu dördüncü yöntem ayrıca eklenen adımın nereye düştüğünü gösteriyor. `degisken` üç komuttur
ve hiçbir adım taşımaz; **çağrısı** ise 16 komut tutar ve bir dizi üretimi taşır. Değişken
sayıda argüman alan bir yöntemin bedeli tanımında değil, **çağrıldığı yerdedir**. Bir biçimin
maliyetini ararken yalnız tanımına bakmak yanıltır.

Tabloda kaynakta **hiç yazılmamış bir yöntem** de var: `<init>`, üç komutluk bir yapıcı.
Kaynakta `Ornek` sınıfı için bir yapıcı yazılmamıştır; sınıf dosyasında bir tane vardır ve
tek işi taban sınıfın yapıcısını çağırmaktır. Bir sonraki ders bu satırın ne zaman
kaybolduğunu ölçer.

En keskin okuma iki satırın karşılaştırmasından çıkar. `diziToplami` ile `listeToplami`
kaynakta **aynı** döngü yazımını taşır — ikisi de gelişmiş `for` döngüsüdür. Dizide bu döngü
**24 komut** ve **sıfır** eklenen adım üretiyor; listede **20 komut** ve **beş** eklenen adım.
Yani **daha az komut, daha çok eklenen adım**: komut sayısı ile eklenen adım sayısı aynı yöne
gitmez. Dizide sanal makinenin doğrudan komutları yeterken, listede bir yineleyici zinciri
kuruluyor — `iterator`, `hasNext`, `next`, bir tip denetimi ve bir `intValue`. İki ölçüm
ayrı şeyler söyler ve bir ölçüm, neyi saydığını söylemeden karşılaştırılamaz.

Aynı gözlem tersinden de kurulabilir. `donguyleBirlestir` ile `diziToplami` **eşit** komut
sayısı taşır — ikisi de 24 — ama biri bir dinamik çağrı taşır, öbürü hiçbir adım taşımaz.
Eşit komut sayısı, eşit iş anlamına gelmiyor. Komut saymak bir ölçüdür; eklenen adım saymak
başka bir ölçüdür ve ikisi birbirinin yerine geçmez.

## Sınırlayıcı Ölçüm: Kaynakta Duran, Sınıf Dosyasında Olmayan

Buraya kadarki her satır bir **ekleme** gösterdi. Tezin sınırı da aynı tabloda duruyor:
`sabitBirlestir` kaynağında bir birleştirme yazar, sınıf dosyasında **2 komut** ve **sıfır**
eklenen adım taşır. Aynı `+` işleci `birlestir`de bir dinamik çağrıya dönüştüğüne göre burada
ne oldu? Sınıf dosyasının komut adlarına bakalım.

```java
// Iz.java — kaynakta duran birleştirme sınıf dosyasında duruyor mu
import java.lang.classfile.*;
import java.lang.classfile.constantpool.*;
import java.util.*;

public class Iz {
    static List<String> opkod(ClassModel cm, String yontem) {
        List<String> a = new ArrayList<>();
        for (MethodModel m : cm.methods())
            if (m.methodName().equalsString(yontem) && m.code().isPresent())
                for (CodeElement e : m.code().get())
                    if (e instanceof Instruction i) a.add(i.opcode().name().toLowerCase(Locale.ROOT));
        return a;
    }

    public static void main(String[] args) throws Exception {
        ClassModel cm = Olcek.derle(Kaynak.ORNEK, "Ornek");
        System.out.println("sabitBirlestir -> " + opkod(cm, "sabitBirlestir"));
        System.out.println("birlestir      -> " + opkod(cm, "birlestir"));
        Set<String> dizgi = new HashSet<>();
        for (PoolEntry pe : cm.constantPool())
            if (pe instanceof StringEntry se) dizgi.add(se.stringValue());
        for (String s : List.of("basit", "ba", "sit"))
            System.out.printf("sinif dosyasinda \"%s\" dizgi sabiti: %s%n", s, dizgi.contains(s));
    }
}
```

```
sabitBirlestir -> [ldc, areturn]
birlestir      -> [aload_0, iload_1, invokedynamic, areturn]
sinif dosyasinda "basit" dizgi sabiti: true
sinif dosyasinda "ba" dizgi sabiti: false
sinif dosyasinda "sit" dizgi sabiti: false
```

`sabitBirlestir` iki komuttan oluşuyor: bir sabit yükle, dön. Ortada birleştirme yoktur.
Dahası, birleştirmenin **işlenenleri** de yoktur: sınıf dosyası `"basit"` dizgisini taşır,
`"ba"` ile `"sit"` orada bulunmaz. İki sabit derleme sırasında tek bir sabite katlanmıştır —
Bilgisayarlar Nasıl Çalışır kursunun kavram olarak kurduğu **sabit katlama (constant
folding)** budur, ve burada kavram değil ölçülen bir yokluk olarak görünüyor.

Böylece kursun ikinci iddiası doğuyor: **eklenen adım her zaman ekleme değildir; bazen
çıkarmadır.** Aynı `+` işleci iki yöntemde iki ayrı sonuç veriyor — birinde sıfır adım,
öbüründe bir dinamik çağrı. Ayrımı yaratan şey işlecin kendisi değil, **işlenenlerinin
derleme zamanında bilinip bilinmediğidir**. Yazılan biçime bakarak sınıf dosyasında ne
olacağını söyleyemeyiz.

## Çevrilen Şey Hangi Makineye Yazıldı

Komut adları ikinci bir şey daha söylüyor. `aload_0` ile `iload_1` bir **yerel değişken
yuvasını (local variable slot)** numarasıyla anar, bir işlemci yazmacını değil. `ldc` bir
değeri **yığıta (stack)** koyar, `areturn` yığıttan alıp döner. Bu komut kümesi bir yığıt
makinesinindir ve hiçbir yerinde gerçek bir donanımın adı geçmez.

Aynı şey adlar için de geçerlidir. Tabloda `listeToplami` satırı `List.iterator` ve
`Iterator.hasNext` yazıyor: sınıf dosyası çağıracağı yöntemi bir adresle değil **adıyla**
taşır. Adın hangi koda karşılık geldiği derleme zamanında değil, dosya yüklenirken çözülür.

Taşınabilirliğin ölçüsü budur: derleyici, donanımı olan bir makineye değil, **belirtimi olan
bir makineye** yazdı. Sınıf dosyası gerçek işlemciye çevrilmemiştir; bu çeviriyi, çalışırken,
sanal makine kendi üstlenir — Bilgisayarlar Nasıl Çalışır kursunun anında derleme başlığıyla
kurduğu iş budur. Bu kursun ölçtüğü şey o ikinci geçiş değil, birincisidir.

## Özet

- Java'da kaynak ile çalıştırılan şey arasında bir derleyici ve bir sınıf dosyası vardır;
  sanal makine kaynağı değil, sınıf dosyasındaki bayt kodu okur.
- Ölçüm çekirdeği `Olcek` bir kaynak metnini derler ve sınıf dosyasını okuyarak her yöntem
  için komut sayısını ve kaynakta yazılmayan adımları döker.
- Yazılan on iki yöntemin **sekizi** eklenen adım taşır, toplam **13 eklenen adım** sayılır;
  üstelik kaynakta hiç yazılmamış üç komutluk bir yapıcı da vardır.
- Aynı gelişmiş `for` döngüsü dizide **24 komut** ve **sıfır** eklenen adım, listede **20
  komut** ve **beş** eklenen adım üretir: komut sayısı ile eklenen adım aynı yöne gitmez.
- Kaynakta duran bir birleştirme sınıf dosyasında hiç yoktur; iki sabit tek sabite katlanır
  ve işlenenler dosyada bulunmaz. Eklenen adım bazen bir çıkarmadır.
- Sınıf dosyasının komutları yuva numarası ve yığıt işlemi anar, çağıracağı yöntemi adıyla
  taşır: çevrilen şey donanımı olan bir makineye değil, belirtimi olan bir makineye yazılmıştır.

## Sonraki Adım

Bu derste sınıf dosyasının **ne olduğu** ölçüldü, ama içindeki komutların ne zaman
çalıştığına hiç bakılmadı. Tabloda kaynakta yazılmamış bir yapıcı vardı ve onun ne zaman
çağrıldığı söylenmedi; bir sınıfın kendi başlatma işinin nerede durduğu da sorulmadı. Sonraki
ders programın yaşam döngüsünü ölçer: sanal makine hangi imzayı **giriş noktası** sayıyor,
sınıf başlatma bloğu ile yapıcı hangi sırayla çalışıyor, ve kaynakta iki ad yazılan bir
sınıfın sınıf dosyasındaki yüzeyi kaç üye taşıyor?
