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.
- Heap — new 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.
GO TO FULL VERSION