Stack, Heap ve Pass-by-Value
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.
-
Bayt: Metoda bir
Userverdim, metot adını değiştirdi. Demek ki nesnenin kendisini verdim! -
Sen: Ama metodun içinde
new User()atayınca benimki hiç değişmedi... -
Bayt: Bir dakika. Aynı nesne hem değişti hem değişmedi mi?!
-
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.
int x = 5; // stack: x = 5User 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.
Bir metodun içinde tanımlanan `int sayac = 0;` nerede yaşar?
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.
Adım adım oku
- main, evin adresini (@1) bir kâğıtta tutuyor.
- modify çağrılınca kâğıdın bir kopyası veriliyor. İki kâğıtta da aynı adres yazıyor.
- Kopya ile gidip evi boyamak mümkün: main de boyalı evi görür.
- Kopyaya yeni bir adres (@2) yazmak ise main'in kâğıdını değiştirmez.
Kopyalanan ne?
static void modify(int n, User user) { n = 99; user.setName("veli"); user = new User("zeynep");} int x = 5;User u = new User("ali");modify(x, u);System.out.println(x + " " + u.getName());Debug
main x ilkel bir değişken: değeri doğrudan stack'te duruyor.
- stack: x
- = 5
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
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.
Java'da `Dog d = new Dog();` yazdığında `d` değişkeni tam olarak neyi tutar?
Bir metot parametre olarak aldığı listeye eleman ekliyor. Çağıran taraf bu eklemeyi görür mü?
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
void main() { - 2
int x = 5; - 3
User u = new User("ali"); - 4
modify(x, u); - 5
// x ve u.name ne olur? - 6
} - 7
- 8
void modify(int n, User user) { - 9
n = 99; - 10
user.setName("veli"); - 11
user = new User("zeynep"); - 12
}
Stack (üstte en son çağrılan)
boş
Heap
boş
Ş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.
- Adımla ve
mainframe’ine bak.modifyiçindekin = 99çalıştıktan sonra bilexhâlâ 5. setNamesatırına gel. Bu kez sağdaki heap nesnesinin içeriği değişiyor — çünkü iki frame de aynı nesneyi gösteriyor.- Son atamayı geç. Yeni nesne sağda beliriyor ama
main’deki ok hiç oynamıyor. - Görevleri tamamla. Simülatörün altındaki üç görev seni üç senaryoda da doğru ana götürüyor.
Her öğeyi yaşadığı bellek bölgesine yerleştir.
Çö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.
Adım adım oku
- Çöp toplayıcı köklerden, yani aktif metotların yerel değişkenlerinden başlar.
- Oklardan giderek ulaşabildiği her nesneyi işaretler: A ve D.
- B ile C birbirini gösteriyor, ama hiçbir kökten onlara yol yok.
- İş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:
// static bir koleksiyon asla temizlenmezse, içindeki her şey sonsuza dek erişilebilirprivate 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.
Java'nın çöp toplayıcısı bir nesnenin toplanabilir olduğuna nasıl karar verir?
Tuzaklar
String pool. String literal’leri JVM’in havuzunda saklanır ve aynı literal her yerde aynı nesneyi verir.
String a = "merhaba";String b = "merhaba";String c = new String("merhaba");
a == b; // true — ikisi de pool'daki aynı nesnea == c; // false — new String pool'u atladı, ayrı nesnea.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:
StackOverflowError | OutOfMemoryError | |
|---|---|---|
| Nerede | Stack | Heap (veya Metaspace) |
| Sebep | Çok derin çağrı zinciri — genelde sonsuz özyineleme | Erişilebilir nesneler heap’e sığmıyor |
| Ayar | -Xss (thread başına stack boyutu) | -Xmx (maksimum heap) |
| İlk bakılacak yer | Stack 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.
Java, nesneleri metotlara referansla (pass-by-reference) geçirir.
Bu program ne yazdırır?
Aklında kalacak üç şey
- 1 Java her zaman kopya verir. Nesnelerde kopyalanan şey nesnenin kendisi değil, adresidir.
- 2 Metot içinde parametreye yeni bir nesne atamak çağıranı etkilemez. O adresteki nesnenin içini değiştirmek ise etkiler.
- 3 Çöp toplayıcı kimsenin ulaşamadığı nesneleri toplar. Birbirini gösteren iki çöp nesne bu yüzden Java'da sızıntı yapmaz.
4 kart sonraki derste seni bekliyor
Bu dersin üstüne kurulanlar
Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.
- Core Java & Eşzamanlılıkfinal ve static — Kilitli Olan ve Ortak OlanBir listeyi final yaptın ama içine eleman eklemeye devam edebiliyorsun. final ne işe yarıyor?Derse git
- Core Java & EşzamanlılıkLambda ve Fonksiyonel Arayüzler — Lambda Neyi Yakalar?Lambda içinde bir sayacı artırmaya çalıştın ve derleyici izin vermedi. Neden?Derse git
- Core Java & Eşzamanlılıkint ve Integer — Kutulu ve Kutusuz Sayılarİki Integer'ı karşılaştırdın: 127'de eşit çıktılar, 128'de çıkmadılar. Sayılar mı değişti?Derse git
- Core Java & EşzamanlılıkGarbage Collection — Üçlü TakasÇöp toplayıcı çalışırken uygulaman neden bir anlığına donar?Derse git