ניהול זיכרון (Stack vs Heap)

שני אזורי זיכרון ב-Java

Java מחלקת את הזיכרון שלה לשני אזורים עיקריים:

  • Stack (מחסנית) - זיכרון "מסודר", מהיר, לנתונים זמניים
  • Heap (ערימה) - זיכרון "חופשי", מנוהל על ידי ה-Garbage Collector

הבנה של ההבדל בין השניים חיונית להבנה איך Java עובדת.

Stack - המחסנית

מה נשמר שם?

  • משתנים מקומיים של פונקציות
  • פרמטרים של פונקציות
  • ערכים פרימיטיביים (int, double, boolean וכו')
  • הפניות לאובייקטים (לא האובייקטים עצמם!)

איך זה עובד?

בכל קריאה לפונקציה, Java יוצרת "מסגרת" (frame) במחסנית עם המשתנים המקומיים והפרמטרים. כשהפונקציה מסיימת - המסגרת נמחקת.

public static void main(String[] args) {
    int a = 5;           // a נשמר ב-stack
    int b = 10;          // b נשמר ב-stack
    int sum = add(a, b); // נוצרת מסגרת חדשה עבור add
}

public static int add(int x, int y) {
    int result = x + y;  // x, y, result נשמרים ב-stack
    return result;
    // המסגרת נמחקת
}

מאפיינים

  • מהיר - מאוד מהיר
  • מוגבל - גודל קטן יחסית
  • אוטומטי - ניהול אוטומטי, אין צורך לשחרר
  • LIFO - Last In First Out

Heap - הערימה

מה נשמר שם?

  • כל האובייקטים (כל מה שנוצר עם new)
  • כל המופעים של מחלקות - String, ArrayList, אובייקטים של מחלקות שלכם
  • מערכים (כן, גם מערכים פרימיטיביים)

איך זה עובד?

כשאתם כותבים new SomeClass(), האובייקט נוצר ב-heap, וההפנייה אליו נשמרת ב-stack.

public static void main(String[] args) {
    Person p = new Person("דוד", 30);
    // p נשמר ב-stack (הפנייה)
    // האובייקט עצמו (Person) נשמר ב-heap
}

דיאגרמה מנטלית

STACK                      HEAP
---------                 ------------------
| p  ----[hpnayah]------> [Person:         ]
---------                 [  name="דוד"    ]
                          [  age=30        ]
                          ------------------

Garbage Collector

Java לא מחייבת אתכם לשחרר זיכרון ידנית. יש רכיב בשם Garbage Collector (GC) שרץ ברקע, מזהה אובייקטים שלא משתמשים בהם יותר, ומוחק אותם.

מתי אובייקט נחשב לא בשימוש? כשאין יותר אף הפנייה אליו.

Person p = new Person("דוד", 30);   // נוצר
p = null;                            // אין הפנייה - ה-GC יאסוף אותו בסופו של דבר

מאפיינים

  • איטי יותר - בגלל ה-GC ומנגנון הקצאה
  • גדול - יכול להכיל הרבה יותר
  • משותף - כל החוטים (threads) ניגשים אליו

דוגמה מלאה

public class MemoryDemo {
    public static void main(String[] args) {
        int x = 5;                          // stack
        String name = "נפתלי";              // הפנייה ב-stack, "נפתלי" ב-heap
        int[] numbers = {1, 2, 3};          // הפנייה ב-stack, המערך ב-heap
        Person p = new Person("דוד", 30);   // הפנייה ב-stack, האובייקט ב-heap

        doSomething(x);
    }

    public static void doSomething(int n) {
        int doubled = n * 2;    // stack - ייעלם כשהפונקציה תסתיים
    }
}

השלכות מעשיות

1. העברת ערכים לפונקציות

public static void modify(int x) {
    x = 100;   // משנה רק את העותק ב-stack המקומי
}

public static void modify(int[] arr) {
    arr[0] = 100;   // שינוי באובייקט ב-heap - ישפיע!
}

2. השוואת אובייקטים

String a = new String("hello");
String b = new String("hello");

a == b           // false! ההפניות שונות
a.equals(b)      // true - אותו תוכן

3. null

null פירושו "אין הפנייה לאובייקט". אם תנסו להשתמש בהפנייה כזו - תקבלו NullPointerException:

String text = null;
int len = text.length();   // NullPointerException!

StackOverflowError

אם קוראים לפונקציה שקוראת לעצמה לנצח - ה-stack נגמר:

public static void endless() {
    endless();   // StackOverflowError
}

זו אחת הסיבות שברקורסיה צריך תנאי עצירה.

OutOfMemoryError

אם ה-heap מתמלא (יותר מדי אובייקטים שלא נאספים):

List<int[]> huge = new ArrayList<>();
while (true) {
    huge.add(new int[1_000_000]);   // OutOfMemoryError
}

סיכום

Stack Heap
משתנים מקומיים אובייקטים
פרימיטיביים מערכים
הפניות תוצאות new
מהיר איטי יותר
אוטומטי Garbage Collector
מוגבל בגודל גדול

כלל אצבע: פרימיטיביים ב-stack, כל דבר שנוצר עם new ב-heap.

בדקו את עצמכם

נסו לענות לבד לפני שאתם פותחים את התשובה.

  1. איפה נוצר אובייקט שנוצר עם new?

    1. ב-Heap, וההפניה אליו נשמרת במשתנה
    2. ב-Stack
    3. בדיסק
    4. בקובץ .class
    הצגת התשובה

    תשובה א. אובייקטים חיים ב-Heap, והמשתנה המקומי ב-Stack מחזיק רק הפניה אליהם.

  2. מי משחרר זיכרון של אובייקטים שכבר לא בשימוש?

    1. המתכנת עם delete
    2. מערכת ההפעלה בסוף התוכנית בלבד
    3. אף אחד
    4. ה-Garbage Collector
    הצגת התשובה

    תשובה ד. ה-GC מזהה אובייקטים שאין אליהם הפניות ומשחרר אותם אוטומטית.