Java String Pool

כפי שידוע לנו, לא נהוג לבדוק ערך של String על ידי == אלא על ידי equals(). הסיבה היא שהשוואת == בודקת האם מדובר באותו מיקום בזיכרון (אותו reference).

אבל בכל זאת הדוגמה הבאה תחזיר לנו true:

String s1 = "hello java";
String s2 = "hello java";

System.out.println(s1 == s2);
System.out.println(s1.equals(s2));

אז מה בעצם קרה פה?

כמו ב-Integer Pool, גם String אינו מוזנח ב-Java. על מנת לחסוך בזיכרון ולנהל משאבים בצורה יעילה, Java שומרת String literals בתוך מאגר מיוחד הנקרא String Pool.

למידע נוסף על Integer Pool:

  • Java Int Pool
  • מה קורה כאשר משתמשים ב-new String?

    אם ניצור String באמצעות new, ההשוואה באמצעות == לא תעבוד, מכיוון שהאובייקט החדש אינו משתמש ברפרנס שנמצא ב-String Pool.

    String s3 = new String("hello java");
    
    System.out.println(s2 == s3);
    System.out.println(s2.equals(s3));

    התוצאה תהיה:

    שימוש ב-intern()

    אם נשתמש בפקודת intern(), ההשוואה בשתי התצורות תחזיר true.

    String s4 = s3.intern();
    
    System.out.println(s2 == s4);
    System.out.println(s2.equals(s4));

    מה בעצם קרה כאן?

    הפקודה intern() ניגשת ל-String Pool ובודקת האם המחרוזת "hello java" כבר קיימת שם.

    שימו לב:
    שימוש לא נכון ב-intern() עלול להכביד על ה-String Pool ולפגוע בביצועים.

    לדוגמה:

    UUID.randomUUID().toString().intern();

    הקוד הזה יכניס אלפי ערכים שונים ל-Pool ויגדיל אותו ללא צורך. מכיוון שכל UUID הוא ייחודי, אין סיבה אמיתית לשמור אותו ב-String Pool.

    אז איך הזיכרון עובד?

    ב-Java קיימים שני אזורי זיכרון עיקריים:

    String Literal

    String s1 = "hello java";

    מה קורה בזיכרון?

    Stack
    └── s1 ───────────► "hello java"

    String Pool
    └── "hello java"

    new String()

    String s3 = new String("hello java");

    כאן ייווצר אובייקט חדש לחלוטין ב-Heap.

    Stack
    └── s3 ───────────► Object #2

    Heap
    └── Object #2 = "hello java"

    String Pool
    └── "hello java"

    Java 7 לעומת Java 8

    התיאור למעלה מתאים ל-Java 8 ומעלה, אך בעבר המצב היה שונה.

    ב-Java 7 ומטה היה אזור מיוחד בשם PermGen (Permanent Generation).

    PermGen היה חלק מהזיכרון של ה-JVM והכיל בעיקר metadata של מחלקות וכן מידע נוסף שהמערכת הגדירה כ"קבוע".

    עם השנים התברר שיש בעיה בגישה זו:

    לכן Java 8 החליפה את PermGen ב-Metaspace.

    Metaspace משתמש ב-Native Memory של מערכת ההפעלה ויכול לגדול בצורה דינמית בהתאם לצורך.

    זה לא אומר שהזיכרון אינסופי – גם כאן ניתן להגיע למגבלה, אך המגבלה גדולה וגמישה בהרבה לעומת PermGen.

    מבנה הזיכרון ב-Java 7

    JVM Memory
    ├── Heap
    │ ├── Young
    │ └── Old
    └── PermGen (fixed)

    מבנה הזיכרון ב-Java 8+

    JVM Memory
    ├── Heap
    │ ├── Young
    │ └── Old
    └── Metaspace (native memory)


    🏠 Back to Orly's Code Corner