CodeGym /Kurslar /JAVA 25 SELF /Lokal dəyişənlər, yaddaş sızmaları, zəif istinadlar

Lokal dəyişənlər, yaddaş sızmaları, zəif istinadlar

JAVA 25 SELF
Səviyyə , Dərs
Mövcuddur

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?
Strong
Yalnız heç bir strong-istinad qalmadıqda Adi dəyişənlər, kolleksiyalar
Soft
Yaddaş çatışmadıqda Uzun müddət saxlamaq istənilən keş
Weak
Heç bir strong-istinad yoxdursa, növbəti GC keçidində Keşlər, WeakHashMap, dinləyicilər
Phantom
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.

1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Zəif istinaddan istifadə – Solan mürəkkəbdə mesaj 👻
Zəif istinaddan istifadə – Solan mürəkkəbdə mesaj 👻
1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
WeakHashMap istifadə edən keş – Unudulmuş Kitabları Yenidən Dönüşdürən Ağıllı Kitabxana 📚
WeakHashMap istifadə edən keş – Unudulmuş Kitabları Yenidən Dönüşdürən Ağıllı Kitabxana 📚
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION