İçeriğe geç

Stack, Heap ve Pass-by-Value

Başlangıç 9 dk Çok sık karşılaşılır

30 saniyede özet

Java bir metoda değişkenin kendisini değil, kopyasını verir. Nesnelerde kopyalanan şey adres olduğu için nesnenin içini değiştirebilirsin, ama çağıranın değişkenini başka bir nesneye çeviremezsin.

Bir metoda nesne verdin, metot onu değiştirdi ve değişikliği sen de gördün. “Demek ki nesnenin kendisini verdim” demek çok doğal, ama yanlış: Java her zaman bir kopya verir.

  1. Bayt: Metoda bir User verdim, metot adını değiştirdi. Demek ki nesnenin kendisini verdim!

  2. Sen: Ama metodun içinde new User() atayınca benimki hiç değişmedi...

  3. Bayt: Bir dakika. Aynı nesne hem değişti hem değişmedi mi?!

  4. Bayt: Sır basit: Java hep kopya verir. Neyin kopyası olduğu her şeyi değiştiriyor.

Stack ve heap

stackHer thread'in kendine ait, metot çağrılarının üst üste yığıldığı bellek alanı. Metot bitince o kare otomatik silinir.Stack üzerinde yerel değişkenler ve referanslar durur. Boyutu sabittir; sonsuz özyineleme burayı doldurunca StackOverflowError alırsın.Sözlükte gör →, her metot çağrısı için bir frame açar. İçinde yerel değişkenler durur ve metot dönünce frame tamamen yok olur.

heapTüm thread'lerin paylaştığı, `new` ile üretilen nesnelerin yaşadığı bellek alanı. Temizliğini garbage collector yapar.Sözlükte gör → ise bütün nesnelerin yaşadığı, bütün thread’lerin paylaştığı alandır.

Kritik ayrım: ilkel tipler değerleriyle stack’te durur, nesneler heap’tedir ve stack yalnızca adreslerini tutar.

Stack'te ne var
int x = 5; // stack: x = 5
User u = new User("ali"); // stack: u = @1 heap: @1 → User{name:"ali"}
İleride işine yarayacak· istersen atla

Her thread kendi stack’ini taşıdığı için yerel değişkenler doğal olarak thread-safe’tir. Heap ise paylaşılır, nesneler bu yüzden korumasızdır; eşzamanlılık derslerinin hepsi bu ayrımın üstüne kurulur.

Kafam karıştı, daha basit anlat

Stack bir not defteri gibi: her metot bir sayfa açar, metot bitince sayfa yırtılır. Heap ise ortak bir depo. Nesneler depoda durur, not defterinde yalnızca raf numarası yazar.

Hızlı kontrolBaşlangıç

Bir metodun içinde tanımlanan `int sayac = 0;` nerede yaşar?

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

Satır satır: kopyalanan ne?

Aşağıdaki metot üç şey yapıyor ve üçü de aynı kuralın farklı yüzleri.

Sence program ne yazdırır — `5 ali` mi, `99 veli` mi, başka bir şey mi? Cevabı göster

5 veli yazdırır. n = 99 yalnızca kopyayı değiştirir, setName paylaşılan nesneye dokunur, son satırdaki yeniden atama ise dışarıdan görünmez.

Bütün ders sekiz saniyede: kâğıt kopyalanır, ev paylaşılır.
Adım adım oku
  1. main, evin adresini (@1) bir kâğıtta tutuyor.
  2. modify çağrılınca kâğıdın bir kopyası veriliyor. İki kâğıtta da aynı adres yazıyor.
  3. Kopya ile gidip evi boyamak mümkün: main de boyalı evi görür.
  4. Kopyaya yeni bir adres (@2) yazmak ise main'in kâğıdını değiştirmez.

Kopyalanan ne?

PassByValue.java
1static void modify(int n, User user) {
2 n = 99;
3 user.setName("veli");
4 user = new User("zeynep");
5}
6
şu an çalışan satırint x = 5;
8User u = new User("ali");
9modify(x, u);
10System.out.println(x + " " + u.getName());

Debug

Adım 1/8

main x ilkel bir değişken: değeri doğrudan stack'te duruyor.

stack: x
= 5
Java 21UTF-8LF7:1

Sol/sağ ok tuşlarıyla da gezebilirsin.

Üçü de aynı kuralın sonucu: değer kopyalanır. İlkel tipte kopyalanan sayı, referenceBir nesnenin heap'teki adresini tutan değer. Java'da değişkenler nesneyi değil, nesneye giden referansı tutar.Sözlükte gör → kopyalanan adrestir.

Aşağıdaki küçük banka programı dersteki üç durumu yan yana çalıştırıyor: referansı yeniden atamak, hesabı değiştirmek ve değiştirilemez bir tutar.

Derinleş · Pass-by-value, üç sınıfta 4 dosya · ~75 satır · ilk okumada atlayabilirsin
Proje dosyaları

src/main/java/bank/ Account.java Değiştirilebilir hesap: heap'te yaşar, referansı olan herkes bakiyesini değiştirebilir.

src/main/java/bank/Account.java
public class Account {
private final String owner;
private BigDecimal balance; // mutable state on the heap
public Account(String owner, BigDecimal balance) {
this.owner = owner;
this.balance = balance;
}
public void credit(BigDecimal amount) {
balance = balance.add(amount);
}
@Override
public String toString() {
return owner + ":" + balance;
}
}

src/main/java/bank/ Money.java Değiştirilemez tutar: her işlem yeni bir nesne döndürür, eskisi olduğu gibi kalır.

src/main/java/bank/Money.java
// Immutable: "changing" it means creating a new one.
public record Money(BigDecimal amount, String currency) {
public Money plus(BigDecimal more) {
return new Money(amount.add(more), currency);
}
}

src/main/java/bank/ Transfers.java Üç metot, üç davranış. Hepsi referansın kopyasını alır.

src/main/java/bank/Transfers.java
public final class Transfers {
private static final BigDecimal EFT_FEE = new BigDecimal("5.00");
private Transfers() {}
// The parameter is a COPY of the caller's reference. Pointing the copy
// at a new object leaves the caller's variable untouched.
static void replace(Account account) {
account = new Account("mallory", new BigDecimal("1000000.00"));
}
// Both copies point to the same heap object: the change is visible to the caller.
static void receiveTransfer(Account account) {
account.credit(new BigDecimal("50.00"));
}
// Records cannot be changed; plus() returns a new object that the caller never sees
// unless it is returned.
static Money addFee(Money money) {
money.plus(EFT_FEE); // result ignored: nothing happens
return money.plus(EFT_FEE); // the only way to hand back the new value
}
}

src/main/java/bank/ Main.java Çıktı yorumlarda: hangi değişiklik çağırana ulaştı?

src/main/java/bank/Main.java
public class Main {
public static void main(String[] args) {
Account alice = new Account("alice", new BigDecimal("100.00"));
Transfers.replace(alice);
System.out.println(alice); // alice:100.00 — reassignment did not escape
Transfers.receiveTransfer(alice);
System.out.println(alice); // alice:150.00 — mutation did
Money amount = new Money(new BigDecimal("20.00"), "TRY");
Money withFee = Transfers.addFee(amount);
System.out.println(amount); // Money[amount=20.00, currency=TRY]
System.out.println(withFee); // Money[amount=25.00, currency=TRY]
int count = 1;
increment(count);
System.out.println(count); // 1 — primitives are copied too
}
static void increment(int value) {
value++;
}
}

Gerçek pass-by-valueJava metoda her zaman değerin kopyasını geçer. Nesnelerde kopyalanan şey nesne değil, referanstır — bu yüzden nesnenin içi değişir ama değişkenin gösterdiği yer değişmez.Sözlükte gör → olsaydı, user = new User("zeynep") satırı main’deki u’yu da değiştirirdi. Değiştirmiyor — ve Java’da swap(a, b) yazılamamasının sebebi tam olarak bu.

Kafam karıştı, daha basit anlat

Metoda hep bir fotokopi verirsin. Fotokopideki adrese gidip evi boyarsa sen de boyayı görürsün. Fotokopiyi karalarsa senin kâğıdın aynı kalır.

Hızlı kontrolBaşlangıç

Java'da `Dog d = new Dog();` yazdığında `d` değişkeni tam olarak neyi tutar?

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

Bir metot parametre olarak aldığı listeye eleman ekliyor. Çağıran taraf bu eklemeyi görür mü?

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

Kendin gör

Simülatör satır satır ilerliyor. Solda stack frame’leri, sağda heap nesneleri.

JVM belleği — stack, heap ve erişilebilirlik

Tohum 1
  1. 1void main() {
  2. 2 int x = 5;
  3. 3 User u = new User("ali");
  4. 4 modify(x, u);
  5. 5 // x ve u.name ne olur?
  6. 6}
  7. 7
  8. 8void modify(int n, User user) {
  9. 9 n = 99;
  10. 10 user.setName("veli");
  11. 11 user = new User("zeynep");
  12. 12}

Stack (üstte en son çağrılan)

boş

Heap

boş

Hız
Adım 0

Şu an ne oldu?

Program başlamadı

Oynat veya adımla. Solda stack frame’leri, sağda heap nesneleri görünecek; her adımda hangi satırın çalıştığı vurgulanacak.

Aklında kalsın: İlkel tipler stack’te değerleriyle, nesneler heap’te durur ve stack yalnızca adreslerini tutar. Bu ayrım bu konudaki her sorunun temelidir.

Görevler0/3

  • Pass-by-value: çağıranın göreceği tek değişikliği bulaçık

    İpucu

    modify içindeki üç satırdan yalnızca biri heap’e dokunuyor. Sağdaki @1 nesnesinin içi değiştiğinde dur.

  • Erişilebilirlik: main bitmeden çöp olan nesneyi yakalaaçık

    İpucu

    Senaryoyu "Erişilebilirlik" yap. Frame hâlâ açıkken bir nesnenin gri kaldığı adımı bul.

  • String pool: main bittikten sonra hayatta kalanı göraçık

    İpucu

    Senaryoyu "String pool" yap ve sonuna kadar ilerle. Bir nesne çöp oldu, diğeri kaldı — hangisi, neden?

Olay günlüğü (0)

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

  1. Adımla ve main frame’ine bak. modify içindeki n = 99 çalıştıktan sonra bile x hâlâ 5.
  2. setName satırına gel. Bu kez sağdaki heap nesnesinin içeriği değişiyor — çünkü iki frame de aynı nesneyi gösteriyor.
  3. Son atamayı geç. Yeni nesne sağda beliriyor ama main’deki ok hiç oynamıyor.
  4. Görevleri tamamla. Simülatörün altındaki üç görev seni üç senaryoda da doğru ana götürüyor.
Hızlı kontrolBaşlangıç

Her öğeyi yaşadığı bellek bölgesine yerleştir.

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

Sınıflandırılmamış

Stack

Thread'e özel, metot dönünce yok olur

    Heap

    Paylaşılan, GC tarafından yönetilir

      Çöpü kim, neye bakarak topluyor?

      Senaryoyu Erişilebilirlik olarak değiştir ve adımla. Önce aşağıdaki sahne bütün fikri tek seferde gösteriyor.

      Sayma yok, takip var: köke bağlı olmayan her şey çöptür.
      Adım adım oku
      1. Çöp toplayıcı köklerden, yani aktif metotların yerel değişkenlerinden başlar.
      2. Oklardan giderek ulaşabildiği her nesneyi işaretler: A ve D.
      3. B ile C birbirini gösteriyor, ama hiçbir kökten onlara yol yok.
      4. İşaretlenmeyen B ve C toplanabilir hâle gelir. Döngü onları kurtarmaz.

      Java’nın çöp toplayıcısı “bu nesneyi kaç kişi gösteriyor?” diye saymaz. Sabit başlangıç noktalarından (kökler) yola çıkar ve ulaşabildiği her nesneyi işaretler.

      Kökler neler?· istersen atla

      Aktif stack frame’lerindeki yerel değişkenler, statik alanlar ve JNI referansları.

      Bir nesneye hiçbir kökten ulaşılamıyorsa toplanabilir hâle gelir.

      Referans sayma yerine erişilebilirlik kullanmanın önemli bir sonucu var: döngüsel referanslar Java’da sızıntı yapmaz. Birbirini gösteren iki nesneye dışarıdan ulaşılamıyorsa ikisi de çöptür.

      Gerçek sızıntılar erişilebilirlikten gelir — hâlâ ulaşılabilen ama bir daha kullanılmayacak nesnelerden:

      Klasik sızıntı
      // static bir koleksiyon asla temizlenmezse, içindeki her şey sonsuza dek erişilebilir
      private static final Map<String, Session> SESSIONS = new HashMap<>();
      Kafam karıştı, daha basit anlat

      Çöp toplayıcı kimseyi saymaz, ipleri takip eder. Çalışan koddan bir nesneye uzanan ip kalmadıysa o nesne atılabilir.

      Hızlı kontrolBaşlangıç

      Java'nın çöp toplayıcısı bir nesnenin toplanabilir olduğuna nasıl karar verir?

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

      Tuzaklar

      String pool. String literal’leri JVM’in havuzunda saklanır ve aynı literal her yerde aynı nesneyi verir.

      StringPool.java
      String a = "merhaba";
      String b = "merhaba";
      String c = new String("merhaba");
      a == b; // true — ikisi de pool'daki aynı nesne
      a == c; // false — new String pool'u atladı, ayrı nesne
      a.equals(c); // true — içerik aynı

      new String("...") neredeyse hiçbir zaman gerekmez; tek etkisi pool’u atlayıp gereksiz bir nesne üretmektir.

      İki hata, iki bölge. StackOverflowError ile OutOfMemoryError farklı yerlerden gelir:

      StackOverflowErrorOutOfMemoryError
      NeredeStackHeap (veya Metaspace)
      SebepÇok derin çağrı zinciri — genelde sonsuz özyinelemeErişilebilir nesneler heap’e sığmıyor
      Ayar-Xss (thread başına stack boyutu)-Xmx (maksimum heap)
      İlk bakılacak yerStack trace’te tekrar eden çağrıHeap dump — hangi nesne birikiyor

      OutOfMemoryError genellikle “heap küçük” demek değildir; “bir şey bırakılmıyor” demektir. Önce heap dump alıp neyin biriktiğine bakılır, sonra -Xmx düşünülür.

      Kendini sına

      Önce hızlı bir ısınma: puan yok, kayıt yok. Sonra asıl sorular.

      Şimşek turu1/5

      Java, nesneleri metotlara referansla (pass-by-reference) geçirir.

      Soru 1/3Başlangıç

      Bu program ne yazdırır?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.
      PassByValue.java
      1int x = 5;
      2User u = new User("ali");
      3
      4modify(x, u);
      5System.out.println(x + " " + u.getName());
      6
      7void modify(int n, User user) {
      8 n = 99;
      9 user.setName("veli");
      10 user = new User("zeynep");
      11}
      Java 21UTF-8LF

      Aklında kalacak üç şey

      1. 1 Java her zaman kopya verir. Nesnelerde kopyalanan şey nesnenin kendisi değil, adresidir.
      2. 2 Metot içinde parametreye yeni bir nesne atamak çağıranı etkilemez. O adresteki nesnenin içini değiştirmek ise etkiler.
      3. 3 Çöp toplayıcı kimsenin ulaşamadığı nesneleri toplar. Birbirini gösteren iki çöp nesne bu yüzden Java'da sızıntı yapmaz.
      Sonraki kapı Bir metni büyük harfe çevirdin ama hiçbir şey değişmedi. Metot çalışmadı mı? String — Hiç Değişmeyen Metin · 8 dk

      4 kart sonraki derste seni bekliyor

      0/5 kart bu dersten toplandı

      Bu dersin üstüne kurulanlar

      Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.