Garbage Collector (GC) ב-Java

בניגוד לשפות כמו C או C++, שבהן אנחנו חייבים לנהל את הזיכרון ידנית (להקצות ולשחרר באמצעות malloc/free או delete), ב-Java יש מנגנון אומפטי שנקרא Garbage Collector. ה JVM עוקב אחר האובייקטים בזיכרון, מזהה אילו אובייקטים כבר אינם בשימוש (כלומר, אין אליהם יותר גישה או הפניות מאף מקום בקוד), ומשחרר את הזיכרון שהם תפסו.

מתי ה GC עובד?

ה GC ב Java עובד מאחורי הקלעים באופן אוטומטי, והוא מנהל את הזיכרון על בסיס הנחה מוכחת שנקראת ההיפוטזה הגנרטיבית החלשה (Weak Generational Hypothesis) רוב האובייקטים מתים צעירים מאוד

איך ה JVM משתמש בזה?

במקום שה GC יסרוק כל פעם מחדש את כל הזיכרון באפליקציה (מה שלוקח המון זמן ועוצר את התוכנית), ה JVM מחלק את ה Heap (הזיכרון) לאזורים לפי גיל האובייקטים דורות (Generations):

החלוקה הזו חוסכת למערכת משאבים עצומים, כי היא מונעת מה GC לבזבז זמן יקר על סריקת אובייקטים ישנים שלא הולכים לשום מקום.

האם ניתן לפנות זיכרון ידנית, האם ניתן ל"קרוא" ל GC?

התשובה הקצרה היא: לא! אבל ניתן לכתוב כמה מונפולציות בקוד שאולי "יקראו" ל GC.

אין ב Java פקודה כמו free(). עם זאת, ישנן שתי דרכים עקיפות שבהן מפתחים מנסים "לעזור" ל GC לרוץ:

  • System.gc(): קריאה למתודה זו ממליצה ל JVM להריץ את ה GC. שימו לב! זו רק המלצה! ה JVM יכול להתעלם ממנה לחלוטין אם הוא החליט שזה לא הזמן המתאים. מומלץ כמעט תמיד להימנע משימוש בה בפרודקשן, כי היא עלולה לגרום לעצירות מיותרות (Stop The World).
  • איפוס הפניות (Nullifying references): אם נקבע null למשתנה שמחזיק אובייקט ענק וכבד שאינכם צריכים עוד, אתם בעצם "מנתקים" את ההפנייה ובכך מאפשרים ל GC לזהות את האובייקט כ"זמין לפינוי" בריצה הבאה.

GC בראי הזמן והתקדמות גרסאות JAVA :

בעולם הישן (Java 8 ומטה):

בעולם המודרני (Java 17 ו 21):

⚠️ שימו לב !

ככל שנתקדם לגרסת Java מתקדמת יותר, מנגנון הפינוי נהיה "חכם" יותר, עובד יותר ברקע במקביל לקוד שלנו, וחוסך לנו צורך לנסות ( וזה אכן ניסיון! כי אין לנו שליטה אמיתית עליו) ולכוון הגדרות זיכרון ידניות.

טבלת אלגוריתמי ה-GC

אלגוריתם ה-GC מתי הוצג / באיזו גרסת Java המאפיין המרכזי למי הוא מתאים?
Serial GC Java 1.0 (ישן מאוד) עובד ב Thread יחיד, מקפיא לחלוטין את כל האפליקציה בזמן הפינוי (Stop The World מלא). אפליקציות קטנות מאוד, מכשירים עם מעבד יחלש ומעט זיכרון.
Parallel GC Java 5 (והיה ברירת המחדל עד Java 8) מנצל מספר מעבדים במקביל כדי לנקות את הזיכרון מהר יותר. הדגש פה הוא על תפוקה (Throughput) ולא על עצירות קצרות. מערכות Backend כבדות שבהן חשוב שהמעבד יעבוד שיא הכוח, ולא אכפת להן מעצירות פתאומיות של שברירי שנייה עד כמה שניות.
G1 GC (Garbage-First) הוצג ב Java 7, הפך לברירת המחדל מ Java 9 ועד Java 21 מחלק את הזיכרון למקטעים קטנים (Regions) ובודק קודם כל את המקטעים עם הכי הרבה "זבל". מנוהל כך שיעמוד ב"יעדי עצירה" מוגדרים מראש. רוב האפליקציות המודרניות הסטנדרטיות שדורשות איזון טוב בין ביצועים לעצירות סבירות.
ZGC הוצג כניסיוני ב Java 11, הפך רשמי ויציב בגרסאות מתקדמות (ובוצעה לו מהפכה ב Java 21) אלגוריתם אולטרה מהיר. רוב העבודה שלו נעשית במקביל (Concurrent) כשהאפליקציה ממשיכה לרוץ, כמעט בלי עצירות Stop The World. מערכות ענק שדורשות זמן תגובה אפסי (Sub millisecond) גם תחת נפחי זיכרון עצומים של מאות ג'יגה בייט.
🏠 Back to Orly's Code Corner