OOPs דחוס ב- JVM

1. סקירה כללית

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

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

2. ייצוג אובייקט זמן ריצה

ה- HotVot JVM משתמש במבנה נתונים הנקרא oops או מצביעי חפצים רגילים לייצג עצמים. אלה אופס מקבילים למצביעים C טבעיים. ה מופעOopהם סוג מיוחד של oop המייצג את מופעי האובייקט ב- Java. יתר על כן, ה- JVM תומך בקומץ אחרים אופס שמורים בעץ המקור של OpenJDK.

בואו נראה איך ה- JVM מתפרש מופעOops בזיכרון.

2.1. פריסת זיכרון אובייקט

פריסת הזיכרון של מופעOop הוא פשוט: זה רק כותרת האובייקט מיד ואחריה אפס או יותר הפניות לשדות מופע.

ייצוג JVM של כותרת אובייקט מורכב מ:

  • מילת סימן אחת משרת מטרות רבות כגון נעילה מוטה, ערכי Hash זהות, ו GC. זה לא oop, אך מסיבות היסטוריות, הוא שוכן ב- OpenJDK oop עץ מקור. כמו כן, מצב מילת הסימן מכיל רק א uintptr_t, לָכֵן, גודלו נע בין 4 ל -8 בתים בארכיטקטורות של 32 סיביות ו -64 סיביות, בהתאמה
  • מילה קלאסית אחת, אולי דחוסה, המייצג מצביע למטא נתונים בכיתה. לפני Java 7 הם הצביעו על ה- דור קבוע, אבל מה- Java 8 ואילך, הם מצביעים על ה- מטאספייס
  • פער של 32 סיביות לאכוף יישור עצמים. זה הופך את הפריסה לידידותית יותר לחומרה, כפי שנראה בהמשך

מיד לאחר הכותרת, יהיו אפס או יותר הפניות לשדות מופע. במקרה זה, א מִלָה זו מילת מכונה מקורית, ולכן 32 סיביות במכונות 32 סיביות מדור קודם ו 64 ביט במערכות מודרניות יותר.

כותרת האובייקט של המערכים, בנוסף למילים לסמן ולסמל, מכילה מילה בת 32 סיביות המייצגת את אורכה.

2.2. אנטומיה של פסולת

נניח שנעבור מארכיטקטורת 32 סיביות מדור קודם למכונת 64 סיביות מודרנית יותר. בהתחלה, אנו עשויים לצפות לקבל דחיפה מיידית לביצועים. עם זאת, זה לא תמיד המקרה כאשר ה- JVM מעורב.

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

אז, האם עלינו לחזור ולהשתמש שוב בארכיטקטורות של 32 סיביות? גם אם זו הייתה אפשרות, לא יכול להיות שיש לנו יותר מ -4 GB של שטח ערימה בחללי תהליך של 32 סיביות בלי קצת יותר עבודה.

3. OOPs דחוסים

כפי שמתברר, ה- JVM יכול להימנע מבזבוז זיכרון על ידי דחיסת מצביעי האובייקט או אופס, כדי שנוכל לקבל את המיטב משני העולמות: המאפשר יותר מ -4 GB של שטח ערימה עם הפניות של 32 סיביות במכונות 64 סיביות!

3.1. אופטימיזציה בסיסית

כפי שראינו קודם, ה- JVM מוסיף ריפוד לאובייקטים כך שגודלם יהיה מכפיל של 8 בתים. עם ריפודים אלה, שלושת החלקים האחרונים פנימה אופס תמיד אפס. הסיבה לכך היא שתמיד מספרים שהם מכפיל של 8 מסתיימים 000 בינארי.

מכיוון ש- JVM כבר יודע ששלושת הביטים האחרונים הם תמיד אפסים, אין טעם לאחסן את האפסים הלא משמעותיים בערימה. במקום זאת, ההנחה היא שהם שם ומאחסנת עוד 3 סיביות משמעותיות יותר שלא יכולנו להתאים ל 32 סיביות בעבר. עכשיו, יש לנו כתובת של 32 סיביות עם 3 אפסים שהועברו ימינה, אז אנחנו דוחסים מצביע של 35 סיביות לכדי 32 סיביות. המשמעות היא שנוכל להשתמש עד 32 GB - 232 + 3 = 235 = 32 GB - של שטח ערימה ללא שימוש בהפניות של 64 סיביות.

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

כדי לאפשר oop אנו יכולים להשתמש ב- -XX: + UseCompressedOops כוונון דגל. ה oop דחיסה היא התנהגות ברירת המחדל מג'אווה 7 ואילך בכל פעם שגודל הערמה המרבי נמוך מ- 32 GB. כאשר גודל הערימה המקסימלי הוא יותר מ 32 GB, ה- JVM יכבה באופן אוטומטי את ה- oop דְחִיסָה. כך שצריך לנהל את השימוש בזיכרון מעבר לגודל ערימה של 32 ג'יגה-בתים בצורה שונה.

3.2. מעבר ל 32 GB

אפשר גם להשתמש במצבעים דחוסים כאשר גדלי הערימה של Java גדולים מ- 32GB. למרות שיישור האובייקט המוגדר כברירת מחדל הוא 8 בתים, ניתן להגדיר ערך זה באמצעות ה- -XX:ObjectAlignmentInBytes כוונון דגל. הערך שצוין צריך להיות עוצמה של שניים ועליו להיות בטווח של 8 ו- 256.

אנו יכולים לחשב את גודל הערימה המרבי האפשרי עם מצביעים דחוסים כדלקמן:

4 GB * ObjectAlignmentInBytes

לדוגמא, כאשר יישור האובייקט הוא 16 בתים, אנו יכולים לנצל עד 64 ג'יגה בייט שטח עם מצביעים דחוסים.

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

3.3. GCs עתידניים

ZGC, תוספת חדשה בג'אווה 11, הייתה אספן אשפה ניסיוני וניתן להרחבה.

זה יכול להתמודד עם טווחים שונים של גדלי ערימה תוך שמירה על הפסקות GC תחת 10 אלפיות השנייה. מכיוון ש- ZGC צריך להשתמש במצבעים צבעוניים של 64 סיביות, הוא אינו תומך בהפניות דחוסות. לכן, באמצעות שקלול חביון נמוך במיוחד כמו ZGC יש לשקול כנגד שימוש בזיכרון רב יותר.

נכון ל- Java 15, ZGC תומך במצבי מחלקה דחוסים אך עדיין חסר תמיכה ב- OOPs דחוסים.

עם זאת, כל האלגוריתמים החדשים של GC לא יחליפו זיכרון בגלל היותם בעלי זמן אחזור נמוך. למשל, Shenandoah GC תומך בהפניות דחוסות בנוסף להיותו GC עם זמני הפסקה נמוכים.

יתר על כן, שנינדואה וגם ZGC מסתיימים נכון ל- Java 15.

4. מסקנה

במאמר זה תארנו א נושא ניהול זיכרון JVM בארכיטקטורות של 64 סיביות. הסתכלנו על מצביעים דחוסים ויישור עצמים, וראינו כיצד ה- JVM יכול לטפל בבעיות אלה, ומאפשר לנו להשתמש בגדלי ערימה גדולים יותר עם מצביעים פחות בזבזניים ומינימום חישוב נוסף.

לדיון מפורט יותר על הפניות דחוסות, מומלץ לבדוק עוד יצירה נהדרת מאת אלכסיי שיפילב. כמו כן, כדי לראות כיצד חלוקת אובייקטים עובדת בתוך ה- HotSpot JVM, עיין במאמר פריסת הזיכרון של אובייקטים ב- Java.