1. Lokal dəyişənlər harada saxlanılır
Ən sadəsindən başlayaq: lokal dəyişənlər. Bunlar metod daxilində elan olunan və yalnız onun icrası zamanı mövcud olan dəyişənlərdir. Onların ömrü qısadır və dramatikdir: metod bitən kimi bütün lokal dəyişənlər izsiz yox olur.
Java-da lokal dəyişənlər stekdə saxlanılır. Hər bir axının — öz steki var. Əgər metod başqa bir metodu çağırırsa, stekə lokal dəyişənlər və geri dönüş ünvanı olan yeni bir «freym» (stack frame) əlavə olunur. Metod işini bitirdikdə, onun freymi stekdən çıxarılır.
Nümunə: lokal dəyişənin həyatı
public class LocalVariableDemo {
public static void main(String[] args) {
int a = 42; // yerli dəyişən a yalnız main-də yaşayır
printSquare(a);
// Burada artıq b dəyişəni yoxdur!
}
public static void printSquare(int b) {
int square = b * b; // yerli dəyişən square
System.out.println("Kvadrat: " + square);
// printSquare-dan çıxandan sonra bütün lokal dəyişənlər yox olur
}
}
Vacib məqam: əgər lokal dəyişən — bu bir istinaddırsa (məsələn, String, Scanner, massiv), onda istinadın özü stekdə yaşayır, obyekt isə heap-də! İstinad yox olduqda və obyektə başqa istinad qalmadıqda, zibil toplayıcı obyekti silə bilər.
İllustrasiya
Stack (main üçün):
| int a = 42 |
| args |
-------------------
Heap:
| [new ilə yaradılmış obyektlər] |
2. Java-da yaddaş sızmaları: mif, yoxsa reallıq?
Bir çox yeni başlayanlar belə düşünür: «Axı Java-da garbage collector (GC) var! O zaman yaddaş sızması ola bilməz!» Təəssüf ki, bu, ilk böyük layihədə dağılacaq bir mifdir.
Zibil toplayıcı yalnız heç bir canlı istinad olmayan obyektləri silir. Haradasa bir istinad qalıbsa (qoy ən gözlənilməz yerdə olsun), obyekt yaddaşda sonadək asılı qalacaq. Və ya OutOfMemoryError-a qədər.
Nümunə 1: Statik kolleksiya-tələ
import java.util.ArrayList;
import java.util.List;
public class MemoryLeakDemo {
// Ah, bu statik kolleksiya!
private static final List<String> BIG_LIST = new ArrayList<>();
public static void main(String[] args) {
for (int i = 0; i < 1_000_000; i++) {
BIG_LIST.add("Sətir nömrəsi " + i);
}
System.out.println("Bir milyon sətir əlavə edildi");
// Hətta main bitdisə belə, JVM işlədiyi müddətdə BIG_LIST yaddaşda qalacaq
}
}
Nə baş verir?
- Statik dəyişən BIG_LIST sinif işlədiyi qədər (bu isə adətən JVM-in ömrünün sonuna kimidir) yaşayır.
- Siyahıya əlavə edilmiş bütün sətirlər zibil toplayıcı ilə silinə bilməz — onlara həmişə BIG_LIST vasitəsilə istinad var.
- Belə kolleksiyaları təmizləməyi təsadüfən unutmusunuzsa — yaddaş sızması əldə edəcəksiniz.
Nümunə 2: Qeydiyyatdan çıxarılmamış dinləyicilər (listeners)
import java.util.ArrayList;
import java.util.List;
class EventSource {
private final List<Runnable> listeners = new ArrayList<>();
public void addListener(Runnable listener) {
listeners.add(listener);
}
// ... digər metodlar ...
}
public class ListenerLeakDemo {
public static void main(String[] args) {
EventSource source = new EventSource();
Runnable listener = () -> System.out.println("Hadisə!");
source.addListener(listener);
// Əgər source.removeListener(listener) çağırmağı unutsaq, listener yaddaşda həmişəlik qalacaq!
}
}
Problem: dinləyici artıq lazım deyil, amma siyahıdan silinməyibsə — o və onun istinad etdiyi bütün obyektlər yaddaşda qalacaq.
Nümunə 3: Heç vaxt sıfırlanmayan keş
import java.util.HashMap;
import java.util.Map;
public class CacheLeakDemo {
private static final Map<String, byte[]> CACHE = new HashMap<>();
public static void main(String[] args) {
for (int i = 0; i < 1_000_000; i++) {
// Hər dəfə 1 KB-lıq massiv yaradırıq
CACHE.put("key" + i, new byte[1024]);
}
System.out.println("Keşə bir milyon element əlavə edildi");
// Keş böyüyür, yaddaş tükənir, OutOfMemoryError!
}
}
Nəticə: GC olsa belə, obyektlərin həyat dövrünə nəzarət etməsəniz, yaddaş sızmasını asanlıqla əldə edə bilərsiniz!
3. Zəif istinadlar (WeakReference) və onların dostları
Bəzən bizə elə keşlər və ya kolleksiyalar lazımdır ki, obyektlərə başqa istinad yoxdursa, zibil toplayıcı onları silə bilsin. Bunun üçün zəif istinadlar (WeakReference) mövcuddur.
Adi (strong) istinadlar
String s = new String("hello"); // strong-istinad
s obyekti yaddaşda, ən azı bir strong-istinad olduğu müddətcə yaşayacaq.
Zəif istinad (WeakReference)
import java.lang.ref.WeakReference;
public class WeakRefDemo {
public static void main(String[] args) {
String strong = new String("Salam, dünya!");
WeakReference<String> weak = new WeakReference<>(strong);
System.out.println("Təmizləmədən əvvəl: " + weak.get()); // istinad var
strong = null; // strong-istinadı götürürük
System.gc(); // GC-dən yaddaşı təmizləməyi xahiş edirik (zəmanətli deyil!)
// Bir müddət sonra weak.get() null ola bilər
System.out.println("GC-dən sonra: " + weak.get());
}
}
Bu necə işləyir?
- Heç olmasa bir strong-istinad olduğu müddətdə GC obyekti silməyəcək.
- Yalnız zəif istinadlar qalıbsa, obyekt növbəti zibil yığımı zamanı silinə bilər.
- weak.get() metodu obyekt hələ canlıdırsa onu, silinibsə null qaytarır.
Zəif istinadlar harada tətbiq olunur?
Əsas tətbiqi — obyektin yaddaşdan silinməsi kritik olmayan keşlərdir. Məsələn, şəkilləri keşləyirsinizsə, amma keşin bütün yaddaşı tutmasını istəmirsinizsə.
Nümunə: WeakHashMap
WeakHashMap — bu, açarların zəif istinadlarla saxlandığı kolleksiyadır. Açarı heç kimə lazım deyilsə, müvafiq cütlük xəritədən silinir.
import java.util.Map;
import java.util.WeakHashMap;
public class WeakHashMapDemo {
public static void main(String[] args) {
Map<Object, String> map = new WeakHashMap<>();
Object key = new Object();
map.put(key, "Dəyər");
System.out.println("Təmizləmədən əvvəl: " + map);
key = null; // açara olan strong-istinadı götürürük
System.gc(); // GC-ni işləməyə çağırırıq
// Bir müddət sonra xəritə boş olacaq!
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
System.out.println("GC-dən sonra: " + map);
}
}
Diqqət: WeakHashMap yalnız açarlar üçün işləyir — dəyərlər adi strong-istinadlarla saxlanılır.
Soft, Weak, Phantom: istinadların bütün ailəsi
Java-da dörd tip istinad var («gücünə» görə sıralanma):
| İstinad növü | Obyekt nə vaxt GC tərəfindən silinir? | Harada tətbiq olunur? |
|---|---|---|
|
Yalnız heç bir strong-istinad qalmadıqda | Adi dəyişənlər, kolleksiyalar |
|
Yaddaş çatışmadıqda | Uzun müddət saxlamaq istənilən keş |
|
Heç bir strong-istinad yoxdursa, növbəti GC keçidində | Keşlər, WeakHashMap, dinləyicilər |
|
Finalizasiyadan sonra, obyektin silinməsini izləmək üçün | Xüsusi tapşırıqlar, heap-dən kənar təmizləmə |
- SoftReference — obyekt yaddaş çatışmadıqda silinir (şəkil keşləri və s. üçün uyğundur).
- WeakReference — başqa istinad yoxdursa, ilk zibil yığımı zamanı silinir.
- PhantomReference — ən «xəyali» tip, mürəkkəb ssenarilər üçün lazımdır, yeni başlayanlar nadir hallarda istifadə edir.
4. Təcrübə: yaddaş sızması nümunəsi və onun düzəldilməsi
Sızma nümunəsi: statik siyahı
import java.util.ArrayList;
import java.util.List;
public class LeakExample {
private static final List<byte[]> list = new ArrayList<>();
public static void main(String[] args) {
for (int i = 0; i < 100_000; i++) {
list.add(new byte[1024 * 1024]); // 1 MB
if (i % 10 == 0) System.out.println("Əlavə olundu " + i + " MB");
}
}
}
Nə baş verəcək?
Proqram tez bir zamanda bütün əlçatan yaddaşı yeyəcək və OutOfMemoryError ilə çökəcək, çünki statik siyahı yaradılmış massivlərə istinadları saxlayır.
Düzəliş: zəif istinadlardən istifadə etmək
Əgər bütün obyektlərin həmişə əlçatan olması kritik deyilsə, onları zəif istinadlarla saxlamaq olar:
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
public class LeakFixed {
private static final List<WeakReference<byte[]>> list = new ArrayList<>();
public static void main(String[] args) {
for (int i = 0; i < 100_000; i++) {
list.add(new WeakReference<>(new byte[1024 * 1024]));
if (i % 10 == 0) System.out.println("Əlavə olundu " + i + " MB");
System.gc(); // GC üçün ipucu (dərhal təmizləməyi zəmanət etmir!)
}
}
}
İndi massivlərə başqa istinad qalmadıqda, GC onları silə bilər — siyahı yalnız zəif istinadları saxlayır.
5. Yaddaş sızmalarının tipik ssenariləri
- Hadisə dinləyiciləri: dinləyicini silməyi unutdunuz — obyekt əbədi yaşayır.
- Statik kolleksiyalar: təmizlənməyən keşlər, qlobal siyahılar — bunların hamısı sızmaya gətirib çıxara bilər.
- Daxili siniflər və lambda-lar: daxili sinif və ya lambda xarici obyektə istinad tutarsa, həmin obyekt xarici obyekt yaşadığı müddətcə silinməyəcək.
Daxili sinif nümunəsi
public class Outer {
private byte[] bigArray = new byte[1024 * 1024 * 100]; // 100 MB
public Runnable createTask() {
// Anonim daxili sinif Outer-ə istinad tutur!
return new Runnable() {
@Override
public void run() {
System.out.println("Tapşırıq icra olunur");
}
};
}
public static void main(String[] args) {
Outer outer = new Outer();
Runnable task = outer.createTask();
// Hətta outer = null olsa belə, task yenə də bigArray-ə istinad tutur!
}
}
Həll: statik daxili siniflərdən istifadə edin və ya məntiqi ayrıca siniflərə çıxarın ki, artıq istinadlar saxlanmasın.
6. Təcrübə: keşdə zəif istinadlardan istifadə
Tədris tətbiqinə WeakHashMap-dən istifadə edən ən sadə keş əlavə edək.
import java.util.Map;
import java.util.WeakHashMap;
public class ImageCache {
private final Map<String, byte[]> cache = new WeakHashMap<>();
public void put(String name, byte[] data) {
cache.put(name, data);
}
public byte[] get(String name) {
return cache.get(name);
}
public static void main(String[] args) {
ImageCache cache = new ImageCache();
cache.put("cat", new byte[1024 * 1024]); // 1 MB
System.out.println("Pişik keşə əlavə edildi");
// Əgər "cat" açarına daha istinad yoxdursa, obyekt GC tərəfindən silinə bilər
}
}
Real tətbiqlərdə (məsələn, şəkil kitabxanalarında) zəif istinadlar nadir istifadə olunan məlumatları avtomatik silməklə yaddaşın dolmasının qarşısını almağa kömək edir.
7. Strong vs Weak: nə zaman hansını istifadə etmək?
- Strong-istinadlar — susmaya görə: zəmanətlə yaşamalı olan hər şey üçün istifadə edin.
- Zəif istinadlar — keşlər, dinləyicilər üçün; obyektin silinməsi kritik deyilsə.
- Soft-istinadlar — yaddaş çatışmayana qədər daha uzun saxlamaq istənilən keşlər üçün.
- Phantom-istinadlar — qabaqcıl ssenarilər üçün (məsələn, heap-dən kənar finalizasiya).
8. Yaddaş və istinadlarla işləyərkən tipik səhvlər
Səhv №1: «GC hər şeyi mənim yerimə edəcək!» Zibil toplayıcı yalnız heç bir canlı strong-istinad olmayan obyektləri silir. Haradasa «unudulmuş» istinad (məsələn, static-kolleksiyada) qalıbsa, obyekt əbədi yaşayacaq.
Səhv №2: Unudulmuş dinləyicilər. Obyektə dinləyici əlavə etdiniz, amma obyekt məhv ediləndə onu silmədiniz? Dinləyici və onun tutduğu hər şey yaddaşda qalacaq.
Səhv №3: Zəif istinadlarsız keş. Avtomatik təmizlənməli keş üçün adi HashMap istifadə edirsiniz? Daha yaxşısı WeakHashMap və ya SoftReference istifadə edin.
Səhv №4: Daxili siniflər və lambda-lar xarici obyekti tutur. Daxili siniflər (və lambda ifadələri) qeyri-açıq şəkildə xarici obyektə istinad saxlayır. Belə siniflərin nümunələrini xarici obyektdən daha uzun saxlayırsınızsa, sızma alacaqsınız.
Səhv №5: GC-nin dərhal işləyəcəyini gözləmək. System.gc() çağırışı zibil yığımının dərhal baş verəcəyini zəmanət etmir. Bu JVM üçün yalnız «xahiş»dir, əmr deyil.
GO TO FULL VERSION