CodeGym /Kurslar /JAVA 25 SELF /JVM-də yaddaşın quruluşu: stack, heap, PermGen/MetaSpace

JVM-də yaddaşın quruluşu: stack, heap, PermGen/MetaSpace

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

1. Java prosesinin yaddaşına ümumi baxış

Java-proqramını işə salanda JVM (Java Virtual Machine) əməliyyat sistemindən bir parça yaddaş istəyir. Bəzən — kiçik, bəzən — xeyli böyük (xüsusən də bir yığın modla hansısa Minecraft işə salırsınızsa). Bu yaddaş bir neçə əsas sahəyə bölünür və hər birinin öz rolu var:

  • Stack — lokal dəyişənlər və metod çağırışları üçün.
  • Heapnew ilə yaratdığınız bütün obyektlər üçün.
  • Xidmət sahələri (PermGen/MetaSpace) — sinif metadataları, statik sahələr və digər «sehrli» şeylər üçün.

Bu təxminən belə görünür:

┌───────────────────────────────┐
│ JVM prosesi                   │
│ ┌─────────────┐               │
│ │  Stack      │ ← Hər axının öz stacki var!
│ └─────────────┘               │
│ ┌─────────────┐               │
│ │  Heap       │ ← Bütün axınlar üçün ümumidir
│ └─────────────┘               │
│ ┌───────────────┐             │
│ │ PermGen/      │ ← Sinif metadataları
│ │ MetaSpace     │
│ └───────────────┘             │
└───────────────────────────────┘

Niyə bu vacibdir?

  • Yaddaşın quruluşunu başa düşmək daha səmərəli və təhlükəsiz kod yazmağa kömək edir.
  • StackOverflowError və ya OutOfMemoryError kimi xətaları diaqnozlaşdırmaq asanlaşır.
  • «garbage collector» və «yaddaş sızması» sözləri qorxulu deyil — harada nə axtarmaq lazım olduğunu anlayırsınız.

2. Stack: sürətli, lokal, amma əbədi deyil

Stack — hər bir axın üçün ayrıca ayrılan xüsusi yaddaş sahəsidir. Stack boşqab yığınına bənzəyir: son qoyulanı — yalnız üstündəkiləri götürdükdən sonra götürəcəksiniz. Yəni stack LIFO (Last In, First Out) prinsipi ilə işləyir.

Stack nə üçündür?

Stackdə saxlanılır:

  • Metodların lokal dəyişənləri (məsələn, metodun daxilində int x = 5;).
  • Metod çağırıldıqdan sonra qayıdış ünvanı (metod bitəndən sonra hara qayıtmalı olduğunu bilmək üçün).

Hər dəfə metod çağıranda stackə yeni bir freym (stack frame) əlavə olunur — sanki həmin metodun bütün lokal dəyişənləri və xidməti məlumatları yerləşən qutu kimidir. Metod işini bitirəndə onun freymi silinir — lokal dəyişənlərin hamısı yox olur.

Nümunə

public static void main(String[] args) {
    int a = 10;           // a main stackindədir
    int b = sum(a, 5);    // sum metodunu çağırırıq
}

public static int sum(int x, int y) {
    int result = x + y;   // x, y, result sum stackindədir
    return result;
}
  • sum çağırılanda onun üçün stackdə ayrıca freym yaradılır.
  • sum bitəndən sonra onun dəyişənləri yox olur.

Dəyişənin həyat dövrü

Lokal dəyişənlər yalnız elan olunduqları metod icra olunduğu müddətdə yaşayır. Metod bitən kimi — onlar artıq yoxdur, yaddaş dərhal azad edilir.

Stackin daşması

Təsadüfən (və ya qəsdən) sonsuz rekursiya yazsanız, hər metod çağırışı stackə yeni freym əlavə edəcək. Bir anda stack tükənəcək və siz aşağıdakı xətanı alacaqsınız:

Exception in thread "main" java.lang.StackOverflowError

Nümunə:

public static void main(String[] args) {
    recurse();
}
public static void recurse() {
    recurse(); // Sonsuz rekursiya!
}

Stackin ölçüsü

Stackin ölçüsü məhduddur — adətən hər axın üçün bir neçə meqabaytdır (parametr -Xss ilə verilə bilər). Stack bitərsə — proqram xəta ilə dayanır.

3. Heap: obyektləriniz üçün məkan

Heap — bütün axınlar üçün ümumi yaddaş sahəsidir; burada new ilə yaratdığınız bütün obyektlər, eləcə də massivlər yaşayır. Obyekt yönümlü proqramlaşdırmanın bütün «sehri» məhz heapdə baş verir.

Obyektlər heapa necə düşür?

String s = new String("Hello");
int[] arr = new int[10];
  • s dəyişəni — istinaddır, stackdə yerləşir.
  • String obyekti və arr massivi — heapdə yerləşir.

Obyektin həyat dövrü

Obyekt heapdə ən azı bir güclü istinad (strong reference) olduqca yaşayır. Obyektə heç kim istinad etməyəndə — o «zibilə» çevrilir və zibil toplayıcı (GC) tərəfindən silinə bilər.

Yaddaşın idarə olunması

C/C++-dan fərqli olaraq, orada yaddaşın azad edilməsinə özünüz diqqət etməlisiniz (free, delete), Java-da bununla GC məşğul olur. Obyekti açıq şəkildə azad edə bilməzsiniz, amma ona olan bütün istinadları null edə bilərsiniz — o zaman silinməyə namizəd olacaq.

Sxem: nə harada yerləşir?

Stack (main)
  └─ s ─┬────────────┐
         │           │
         ▼           │
      Heap           │
   ┌─────────────┐   │
   │ String "Hello"◄──┘
   └─────────────┘

Heap-in xüsusiyyətləri

  • Heap JVM prosesində hamı üçün birdir.
  • Heap-in ölçüsünü işə salarkən təyin etmək olar (-Xmx, -Xms).
  • Əgər heapdə boş yer qalmayıbsa və GC yaddaşı azad edə bilmirsə — proqram OutOfMemoryError xətası ilə dayanır.

4. PermGen və MetaSpace: siniflər harada «yaşayır»?

class MyClass { ... } yazanda və proqramı işə salanda JVM bu siniflə bağlı hər şeyi — metodları, sahələri, baytkodu, statik dəyişənləri, konstantları və hətta sətir literalını — haradasa saxlamalıdır. Bunun üçün JVM-də siniflərin «yaşadığı» xüsusi yaddaş sahəsi var.

Əvvəllər, Java 8-dən öncə, bu sahə PermGen (Permanent Generation) adlanırdı. Amma onda xeyli problem var idi — məsələn, ölçüsü sabit idi və yer çatmayanda tətbiq OutOfMemoryError: PermGen space xətası ilə sadəcə uçurdu.

Java 8 ilə daha çevik bir sahə — MetaSpace yarandı. O, köhnə PermGen-i əvəz etdi və indi avtomatik genişlənə bilər; sistemə nə qədər lazımdırsa (mövcud fiziki yaddaş çərçivəsində) o qədər yaddaş götürə bilər.

PermGen (Java 8-dən əvvəl)

  • PermGen-də sinif metadataları, statik sahələr, sətir literalları saxlanırdı.
  • PermGen-in ölçüsü məhdud idi (susmaya görə kiçik), onu -XX:MaxPermSize=256m parametri ilə artırmaq olardı.
  • Əgər tətbiq dinamik olaraq çoxlu sinif yükləyirdisə (məsələn, veb-serverlərdə), PermGen «bitə» bilərdi və siz bu xətanı alırdınız:
java.lang.OutOfMemoryError: PermGen space
  • Problem: PermGen-in təmizlənməsi hər zaman düzgün baş vermirdi, əgər siniflər dinamik yüklənib-boşaldılırdısa (məsələn, veb-tətbiqlər yenidən yüklənəndə).

MetaSpace (Java 8+)

  • Java 8-dən PermGen yox oldu və MetaSpace yarandı.
  • MetaSpace sinif metadatalarını saxlayır, amma artıq native yaddaşda (Java Heap-in kənarında).
  • MetaSpace-in ölçüsü susmaya görə məhdud deyil (yalnız sistem yaddaşı ilə məhdudlaşır), lakin limiti -XX:MaxMetaspaceSize=512m ilə təyin etmək olar.
  • Yaddaş çatışmazlığı zamanı xəta indi belə görünür:
java.lang.OutOfMemoryError: Metaspace
  • MetaSpace-ə həmçinin statik sahələr, metodlar, sinif haqqında məlumatlar düşür.

Sxem: hər şey necə qurulub

┌───────────────────────────────┐
│ JVM prosesi                   │
│ ┌─────────────┐               │
│ │  Stack      │ ← Lokal dəyişənlər, metod çağırışları
│ └─────────────┘               │
│ ┌─────────────┐               │
│ │  Heap       │ ← Obyektlər, massivlər, new ilə yaradılan hər şey
│ └─────────────┘               │
│ ┌───────────────┐             │
│ │ MetaSpace     │ ← Sinif metadataları, statik sahələr
│ └───────────────┘             │
└───────────────────────────────┘

Niyə bu vacibdir?

Əgər adi desktop və ya server tətbiqləri yazırsınızsa, yəqin ki, heç vaxt PermGen və ya MetaSpace xətaları ilə rastlaşmayacaqsınız. Amma dinamik sinif yükləmə ilə işləyirsinizsə (məsələn, plaginlər, veb-tətbiqlər, Spring kimi frameworklər, hansı ki, bir yığın sinfi yükləyib boşalda bilər), MetaSpace haqqında bilik — must‑have!

5. İllustrasiya: JVM yaddaşının sxemi

flowchart TD
    subgraph JVM
        direction TB
        Stack1["Stack (Thread 1)"]
        Stack2["Stack (Thread 2)"]
        Heap[Heap]
        MetaSpace[MetaSpace]
    end
    Stack1 --istinad edir--> Heap
    Stack2 --istinad edir--> Heap
    Heap --siniflərdən istifadə edir--> MetaSpace
  • Hər axının öz stacki var.
  • Bütün stacklər heapdəki obyektlərə istinad edə bilər.
  • Heapdəki obyektlər öz siniflərini «bilir», həmin məlumat MetaSpace-də saxlanılır.

6. Nümunə: real kodda bu necə görünür

public class MemoryDemo {
    public static void main(String[] args) {
        int x = 42; // x main stackindədir
        String s = "Hello!"; // s — stekdə istinaddır, String obyekti heapdədir, "Hello!" literalı MetaSpace-dədir
        Person p = new Person("Alice"); // p — stekdə istinaddır, Person obyekti heapdədir

        // Yeni stack frame yaratmaq üçün metodu çağıraq
        printPerson(p);
    }

    public static void printPerson(Person person) {
        // person — printPerson stackindəki istinaddır
        System.out.println(person.getName());
    }
}

class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
    public String getName() { return name; }
}

İzah:

  • x — lokal dəyişəndir, main metodunun stackində yaşayır.
  • s — stekdə istinaddır, String obyekti heapdədir, "Hello!" sətir literalı — MetaSpace-dədir.
  • p — stekdə istinaddır, Person obyekti heapdədir.
  • Person sinfi və onun bütün metod/sahələri — MetaSpace-dədir (sinif metadataları).
  • printPerson(p) çağırışı yeni stack-freym yaradır; onun daxilində lokal istinad person heapdəki eyni obyektə işarə edir.

7. JVM yaddaşı necə idarə edir: qısa FAQ

Stacki idarə edə bilərəm?
Xeyr, stack tamamilə JVM-in nəzarətindədir. Siz yalnız işə salarkən onun ölçüsünü verə bilərsiniz (-Xss).

Heap-i idarə edə bilərəm?
Qismən: heap-in ölçüsü işə salarkən verilir (-Xmx, -Xms). Təmizləmə ilə zibil toplayıcı (GC) məşğul olur.

MetaSpace-i idarə edə bilərəm?
Ölçünü məhdudlaşdıra bilərsiniz (-XX:MaxMetaspaceSize), amma adətən buna ehtiyac olmur.

Yaddaş çatışmayanda nə baş verir?
— Stack bitərsə — StackOverflowError.
— Heap bitərsə — OutOfMemoryError: Java heap space.
— MetaSpace bitsə — OutOfMemoryError: Metaspace.

8. Yaddaşla işləyərkən tipik səhvlər

Xəta №1: Sonsuz rekursiya səbəbindən StackOverflowError. Ən çox rast gəlinən səbəb — rekursiyadan çıxış şərtini nəzərə almamaqdır. Məsələn, metod özünü dayanmadan çağırır. JVM stacki sonsuzadək böyüdə bilməyəcək və proqram çökəcək.

Xəta №2: Heap daşması səbəbindən OutOfMemoryError. Əgər çoxlu obyekt yaradırsınızsa və onlara hələ də dəyişənlər/koleksiyalar istinad edirsə (məsələn, list-ə elementlər əlavə edirsiniz, amma heç vaxt silmirsiniz), heap tükənə bilər.

Xəta №3: OutOfMemoryError: PermGen space / Metaspace. Plaginlərdən istifadə edir və ya dinamik olaraq çoxlu sinif yükləyirsinizsə, MetaSpace təmizlənmirsə (məsələn, siniflərin səhv boşaldılması səbəbi ilə), MetaSpace-də yer bitə bilər.

Xəta №4: İstinadla obyektin qarışdırılması. Bir çox yeni başlayanlar bunu qarışdırır: Person tipli stekdəki dəyişən — yalnız istinaddır, obyektin özü isə heapdədir.

Xəta №5: Zibil toplayıcının hər şeyi dərhal siləcəyini gözləmək. GC «ovqatla» işləmir, o daxili alqoritmlərə və yaddaş çatışmazlığına əsasən işləyir; istinadı null etdiyiniz kimi dərhal silinmə baş vermir. Yaddaşın anında azad ediləcəyinə güvənməyin.

1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
StackOverflowError – Rekursiyanın Şıltaq Kaskadı 🧙‍♂️
StackOverflowError – Rekursiyanın Şıltaq Kaskadı 🧙‍♂️
1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
OutOfMemoryError – Yaddaşın Acgöz Yeyəni 💾
OutOfMemoryError – Yaddaşın Acgöz Yeyəni 💾
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION