מדידת גדלי אובייקטים ב- JVM

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

במדריך זה, נראה כמה מקום כל אובייקט צורך בערמת ג'אווה.

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

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

במדריך זה אנו מתמקדים ביישום JVM ספציפי אחד: ה- HotSpot JVM.

אנו משתמשים במונחי JVM ו- HotSpot JVM לסירוגין לאורך כל ההדרכה.

2. גדלי אובייקט רדודים, שמורים ועמוקים

כדי לנתח את גדלי האובייקטים, אנו יכולים להשתמש בשלושה מדדים שונים: גדלים רדודים, שמורים ועמוקים.

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

כפי שמוצג לעיל, הגודל הרדוד של ה- לְשַׁלֵשׁ מופע הוא רק סכום של שלוש הפניות. אנו לא כוללים את הגודל האמיתי של האובייקטים המופנים, כלומר A1, B1, ו C1, מגודל זה.

לעומת זאת, הגודל העמוק של האובייקט כולל את הגודל של כל האובייקטים המופנים, בנוסף לגודל הרדוד:

כאן הגודל העמוק של לְשַׁלֵשׁ מופע מכיל שלוש הפניות בתוספת הגודל האמיתי של A1, B1, ו C1. לכן, גדלים עמוקים הם רקורסיביים באופיים.

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

הגודל השמור של לְשַׁלֵשׁ מופע כולל רק A1 ו C1 בנוסף ל לְשַׁלֵשׁ מופע עצמו. מצד שני, גודל שמור זה אינו כולל את B1, מאז זוג למשל יש התייחסות ל B1.

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

כדי להבין טוב יותר את הגודל שנשמר, עלינו לחשוב במונחים של אוסף האשפה. איסוף ה- לְשַׁלֵשׁ מופע עושה את A1 ו C1 בלתי ניתן להשגה, אבל B1 עדיין ניתן להגיע אליו באמצעות אובייקט אחר. בהתאם למצב, הגודל הנשמר יכול להיות בכל מקום בין הגודל הרדוד והעמוק.

3. תלות

כדי לבדוק את פריסת הזיכרון של אובייקטים או מערכים ב- JVM, נשתמש בכלי Java Object Layout (JOL). לכן נצטרך להוסיף את ה- jol-core תלות:

 org.openjdk.jol jol-core 0.10 

4. סוגי נתונים פשוטים

כדי להבין טוב יותר את גודל האובייקטים המורכבים יותר, ראשית עלינו לדעת כמה מקום כל סוג נתונים פשוט צורך. לשם כך, אנו יכולים לבקש מ- Java Memory Layout או JOL להדפיס את מידע ה- VM:

System.out.println (VM.current (). פרטים ());

הקוד שלעיל ידפיס את גדלי סוגי הנתונים הפשוטים כדלקמן:

# הפעלת 64-סיביות HotSpot VM. # שימוש ב- oop דחוס עם משמרת של 3 סיביות. # שימוש בקלאס דחוס עם משמרת של 3 סיביות. # אובייקטים מיושרים 8 בתים. # גדלי שדה לפי סוג: 4, 1, 1, 2, 2, 4, 4, 8, 8 [בתים] # גדלי רכיבי מערך: 4, 1, 1, 2, 2, 4, 4, 8, 8 [בתים ]

אז הנה דרישות השטח לכל סוג נתונים פשוט ב- JVM:

  • הפניות לאובייקטים צורכות 4 בתים
  • בוליאני ו בתים ערכים צורכים 1 בית
  • קצר ו לְהַשְׁחִיר ערכים צורכים 2 בתים
  • int ו לָצוּף ערכים צורכים 4 בתים
  • ארוך ו לְהַכפִּיל ערכים צורכים 8 בתים

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

ראוי גם להזכיר כי כל סוגי הנתונים צורכים כמות זהה של זיכרון כאשר הם משמשים לסוגי רכיבי מערך.

4.1. הפניות לא דחוסות

אם נשבית את הפניות הדחוסות באמצעות -XX: -UseCompressedOops כוונון הדגל, ואז דרישות הגודל ישתנו:

# אובייקטים מיושרים 8 בתים. # גדלי שדה לפי סוג: 8, 1, 1, 2, 2, 4, 4, 8, 8 [בתים] # גדלי רכיבי מערך: 8, 1, 1, 2, 2, 4, 4, 8, 8 [בתים ]

כעת הפניות לאובייקטים יצרכו 8 בתים במקום 4 בתים. סוגי הנתונים הנותרים עדיין צורכים את אותה כמות זיכרון.

יתר על כן, ה- HotSpot JVM גם אינו יכול להשתמש בהפניות הדחוסות כאשר גודל הערמה גדול מ- 32 GB (אלא אם כן נשנה את יישור האובייקט).

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

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

5. אובייקטים מורכבים

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

קורס בכיתה ציבורית {שם מחרוזת פרטי; // בונה}

כל אחד פּרוֹפֶסוֹר, בנוסף לפרטים האישיים, יכולה להיות רשימה של קוּרסs:

פרופסור בכיתה ציבורית {שם מחרוזת פרטי; קבועה בוליאנית פרטית; קורסי רשימה פרטית = ArrayList חדש (); רמת אינטראקציה פרטית; פרטי LocalDate לידת יום; אחרון כפול פרטי; // בונה}

5.1. גודל רדוד: קוּרס מעמד

הגודל הרדוד של קוּרס מופעי מחלקה צריכים לכלול הפניה של אובייקט בן 4 בתים (עבור שֵׁם שדה) בתוספת תקורה של אובייקט. אנו יכולים לבדוק הנחה זו באמצעות JOL:

System.out.println (ClassLayout.parseClass (Course.class) .toPrintable ());

פעולה זו תדפיס את הדברים הבאים:

פנימי של אובייקט קורס: OFFSET SIZE סוג סוג תיאור ערך 0 12 (כותרת אובייקט) לא זמין 12 4 java.lang. מחרוזת Course.name שם לא מתאים גודל מופע: 16 בתים הפסדי שטח: 0 בתים פנימיים + 0 בתים חיצוניים = 0 בתים סה"כ

כפי שמוצג לעיל, הגודל הרדוד הוא 16 בתים, כולל התייחסות לאובייקט של 4 בתים ל- שֵׁם שדה בתוספת כותרת האובייקט.

5.2. גודל רדוד: פּרוֹפֶסוֹר מעמד

אם אנו מריצים את אותו קוד עבור ה- פּרוֹפֶסוֹר מעמד:

System.out.println (ClassLayout.parseClass (Professor.class) .toPrintable ());

אז JOL תדפיס את צריכת הזיכרון עבור ה- פּרוֹפֶסוֹר בכיתה כמו הבאה:

פנימי אובייקט פרופסור: קיזוז גודל סוג תיאור ערך 0 12 (כותרת האובייקט) לא זמין 12 4 int פרופסור. רמת לא מתאים 16 8 פרופסור כפול. אחרון הערכה לא מתאים 24 1 פרופסור בוליאני. קבוע לא מתאים 25 3 (יישור / פער ריפוד) 28 4 java.lang. מחרוזת פרופסור.שם לא שם 32 4 java.util.List פרופסור.קורסים לא מתאים 36 4 java.time.LocalDate פרופסור.לידה יום לא מתאים גודל המופע: 40 בתים הפסדי מקום: 3 בתים פנימיים + 0 בתים חיצוניים = 3 בתים בסך הכל

כפי שציפינו כנראה, השדות המקופלים צורכים 25 בתים:

  • שלוש הפניות לאובייקט, שכל אחת מהן צורכת 4 בתים. אז 12 בתים בסך הכל להתייחסות לאובייקטים אחרים
  • אחד int אשר צורכת 4 בתים
  • אחד בוליאני אשר צורכת 1 בתים
  • אחד לְהַכפִּיל אשר צורכת 8 בתים

הוספת 12 הבתים התקורה של כותרת האובייקט בתוספת 3 בתים של ריפוד יישור, הגודל הרדוד הוא 40 בתים.

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

5.3. גודל רדוד: מקרה

ה מידה של() השיטה ב- JOL מספקת דרך הרבה יותר פשוטה לחישוב הגודל הרדוד של מופע אובייקט. אם אנו מריצים את קטע הקוד הבא:

מחרוזת ds = "מבני נתונים"; קורס קורס = קורס חדש (ד '); System.out.println ("הגודל הרדוד הוא:" + VM.current (). SizeOf (קורס));

היא תדפיס את הגודל הרדוד כדלקמן:

הגודל הרדוד הוא: 16

5.4. גודל לא דחוס

אם נשבית את הפניות הדחוסות או נשתמש ביותר מ- 32 ג'יגה-בתים של הערימה, הגודל הרדוד יגדל:

פנימיות אובייקט פרופסור: קיזוז גודל סוג תיאור ערך 0 16 (כותרת אובייקט) לא זמין 16 8 פרופסור כפול. אחרון הערכה לא זמין 24 4 פרופסור. רמת לא / לא 28 1 פרופסור בוליאני. סבר לא מתאים 29 3 (יישור / פער ריפוד) 32 8 java.lang. מחרוזת פרופסור.שם לא נכון 40 8 java.util.List פרופסור.קורסים לא מתאים 48 8 java.time.LocalDate פרופסור.לידה יום לא מתאים גודל המופע: 56 בתים הפסדי מקום: 3 בתים פנימיים + 0 בתים חיצוניים = 3 בתים בסך הכל

כאשר ההפניות הדחוסות מושבתות, כותרת האובייקט והפניות לאובייקט יצרכו זיכרון רב יותר. לכן, כפי שמוצג לעיל, עכשיו אותו הדבר פּרוֹפֶסוֹר בכיתה צורכת 16 בתים נוספים.

5.5. גודל עמוק

כדי לחשב את הגודל העמוק, עלינו לכלול את הגודל המלא של האובייקט עצמו וכל משתפי הפעולה שלו. למשל, לתרחיש פשוט זה:

מחרוזת ds = "מבני נתונים"; קורס קורס = קורס חדש (ד ');

הגודל העמוק של קוּרס מופע שווה לגודל הרדוד של ה- קוּרס המופע עצמו בתוספת הגודל העמוק של אותו מסוים חוּט למשל.

עם זה נאמר, בואו נראה כמה מקום זה חוּט מופע צורכת:

System.out.println (ClassLayout.parseInstance (ds) .toPrintable ());

כל אחד חוּט מופע מקפל א לְהַשְׁחִיר[] (עוד על כך בהמשך) ו- int קוד hash:

java.lang. פנימי אובייקט מחרוזת: OFFSET SIZE TYPE תיאור ערך 0 4 (כותרת עצם) 01 00 00 00 4 4 (כותרת עצם) 00 00 00 00 8 4 (כותרת עצם) da 02 00 f8 12 4 char [] מחרוזת. ערך [D, a, t, a,, S, t, r, u, c, t, u, r, e, s] 16 4 int String.hash 0 20 4 (אובדן בגלל יישור האובייקט הבא) גודל: 24 בתים הפסדי שטח: 0 בתים פנימיים + 4 בתים חיצוניים = 4 בתים בסך הכל

הגודל הרדוד של זה חוּט המופע הוא 24 בתים, הכוללים את 4 הבתים של קוד hash במטמון, 4 בתים של לְהַשְׁחִיר[] הפניה ותקורות אובייקט אופייניות אחרות.

כדי לראות את הגודל האמיתי של לְהַשְׁחִיר[], נוכל לנתח גם את פריסת הכיתות שלה:

System.out.println (ClassLayout.parseInstance (ds.toCharArray ()). ToPrintable ());

הפריסה של לְהַשְׁחִיר[] נראה ככה:

[C פנימי של אובייקט: OFFSET SIZE סוג סוג תיאור ערך 0 4 (כותרת עצם) 01 00 00 00 4 4 (כותרת עצם) 00 00 00 00 8 4 (כותרת עצם) 41 00 00 f8 12 4 (כותרת עצם) 0f 00 00 00 16 30 char [C. לא רלוונטי 46 2 (אובדן בגלל יישור האובייקט הבא) גודל מופע: 48 בתים הפסדי שטח: 0 בתים פנימיים + 2 בתים חיצוניים = 2 בתים סה"כ

אז יש לנו 16 בתים עבור ה- קוּרס למשל, 24 בתים עבור ה- חוּט למשל, ולבסוף 48 בתים עבור ה- לְהַשְׁחִיר[]. בסך הכל, הגודל העמוק של זה קוּרס מופע הוא 88 בתים.

עם הכנסת מיתרים קומפקטיים ב- Java 9, ה- חוּט הכיתה משתמשת באופן פנימי ב- בתים [] לאחסון הדמויות:

java.lang. פנימי אובייקט מחרוזת: OFFSET גודל גודל תיאור 0 4 (כותרת אובייקט) 4 4 (כותרת אובייקט) 8 4 (כותרת אובייקט) 12 4 בתים [] String.value # the array byte 16 4 int String.hash 20 1 בתים String.coder # encodig 21 3 (אובדן בגלל יישור האובייקט הבא)

לכן, ב- Java 9+, טביעת הרגל הכוללת של קוּרס מופע יהיה 72 בתים במקום 88 בתים.

5.6. פריסת גרף אובייקטים

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

למשל, אנו יכולים לראות את טביעת הרגל הכוללת של קוּרס למשל כדלקמן:

System.out.println (GraphLayout.parseInstance (קורס). ToFootprint ());

שמדפיס את הסיכום הבא:

[e-mail protected] טביעת רגל: COUNT AVG SUM DESCRIPTION 1 48 48 [C 1 16 16 com.baeldung.objectsize.Course 1 24 24 java.lang. מחרוזת 3 88 (סה"כ)

זה 88 בתים בסך הכל. ה גודל כולל() השיטה מחזירה את טביעת הרגל הכוללת של האובייקט, שהיא 88 בתים:

System.out.println (GraphLayout.parseInstance (קורס) .totalSize ());

6. מכשור

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

מחלקה ציבורית ObjectSizeCalculator {מכשירים פרטיים של מכשור סטטי; קדם בטל סטטי ציבורי (טענות מחרוזת, אינסטרומנטציה אינסט) {אינסטרומנטציה = אינסט; } ציבורי סטטי ארוך ארוךOf (Object o) {return instrumentation.getObjectSize (o); }}

כפי שמוצג לעיל, נשתמש ב- getObjectSize () שיטה למצוא את הגודל הרדוד של אובייקט. אנו זקוקים גם לקובץ מניפסט:

Premain-Class: com.baeldung.objectsize.ObjectSizeCalculator

ואז משתמשים בזה MANIFEST.MF אנו יכולים ליצור קובץ JAR ולהשתמש בו כסוכן Java:

$ jar cmf MANIFEST.MF agent.jar * .class

לבסוף, אם אנו מריצים קוד כלשהו עם ה- -javaagent: /path/to/agent.jar אז אנחנו יכולים להשתמש ב- מידה של() שיטה:

מחרוזת ds = "מבני נתונים"; קורס קורס = קורס חדש (ד '); System.out.println (ObjectSizeCalculator.sizeOf (קורס));

פעולה זו תדפיס 16 כגודל הרדוד של קוּרס למשל.

7. סטטיסטיקה כיתתית

כדי לראות את הגודל הרדוד של האובייקטים ביישום שכבר פועל, אנו יכולים להסתכל על הנתונים הסטטיסטיים בכיתה באמצעות ה- jcmd:

$ jcmd GC.class_stats [output_column]

לדוגמא, אנו יכולים לראות כל גודל ומופע של כל המופעים קוּרס מקרים:

$ jcmd 63984 GC.class_stats InstSize, InstCount, InstBytes | grep קורס 63984: InstSize InstCount InstBytes ClassName 16 1 16 com.baeldung.objectsize.Course

שוב, זה מדווח על הגודל הרדוד של כל אחד מהם קוּרס מופע כמו 16 בתים.

כדי לראות את הסטטיסטיקה בכיתה, עלינו להפעיל את היישום עם ה- -XX: + UnlockDiagnosticVMOptions כוונון דגל.

8. Hump Dump

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

$ jcmd GC.heap_dump [אפשרויות] / path / to / dump / file

לדוגמה:

$ jcmd 63984 GC.heap_dump -all ~ / dump.hpro

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

לאחר קבלת המזבלה, אנו יכולים לייבא אותה לכלים כמו Visual VM:

כפי שמוצג לעיל, הגודל השמור של היחיד קוּרס מופע הוא 24 בתים. כפי שצוין קודם, הגודל הנשמר יכול להיות בכל מקום בין רדוד (16 בתים) לגדלים עמוקים (88 בתים).

ראוי גם להזכיר כי ה- Visual VM היה חלק מהפצות ה- Oracle ו- Open JDK לפני Java 9. עם זאת, זה כבר לא המקרה לגבי Java 9, ועלינו להוריד את ה- Visual VM מאתר האינטרנט שלו בנפרד.

9. מסקנה

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

כרגיל, כל הדוגמאות זמינות ב- GitHub.


$config[zx-auto] not found$config[zx-overlay] not found