מדריך לשיטת הגמר ב- Java

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

במדריך זה נתמקד בהיבט הליבה של שפת Java - ה- לְסַכֵּם השיטה המסופקת על ידי השורש לְהִתְנַגֵד מעמד.

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

2. שימוש בגמר גמר

ה לְסַכֵּם() השיטה נקראת finalizer.

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

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

כדי להבין כיצד עובד גמר, בואו נסתכל על הצהרת כיתה:

מחלקה ציבורית ניתן לסיים {קורא BufferedReader פרטי; פומבי שניתן לסיים () {קלט InputStream = this.getClass () .getClassLoader () .getResourceAsStream ("file.txt"); this.reader = BufferedReader חדש (InputStreamReader חדש (קלט)); } מחרוזת ציבורית readFirstLine () זורקת IOException {String firstLine = reader.readLine (); חזור firstLine; } // חברים אחרים בכיתה}

הכיתה ניתן לסיים יש שדה קוֹרֵא, המתייחס למשאב הניתן לסגירה. כאשר נוצר אובייקט מהמחלקה הזו, הוא בונה חדש BufferedReader קריאה למשל מקובץ שבכיתה.

מופע כזה משמש ב- readFirstLine שיטה לחילוץ השורה הראשונה בקובץ הנתון. שימו לב שהקורא אינו סגור בקוד הנתון.

אנו יכולים לעשות זאת באמצעות קצה הגמר:

@ ביטול חלל ציבורי לסיים () {נסה {reader.close (); System.out.println ("סגור BufferedReader בגמר הגמר"); } לתפוס (IOException e) {// ...}}

קל לראות ש- Finalizer מוכרז בדיוק כמו כל שיטת מופע רגילה.

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

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

  1. זה יקר
  2. זה לא מפעיל את איסוף האשפה מיד - זה רק רמז ל- JVM להתחיל את GC
  3. JVM יודע טוב יותר מתי צריך להתקשר ל- GC

אם נצטרך לכפות GC, נוכל להשתמש jconsole בשביל זה.

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

@ מבחן ציבורי בטל כאשר GC_thenFinalizerExecuted () זורק IOException {String firstLine = new Finalizable (). ReadFirstLine (); assertEquals ("baeldung.com", firstLine); System.gc (); }

בהצהרה הראשונה, א ניתן לסיים האובייקט נוצר, ואז שלו readFirstLine שיטה נקראת. אובייקט זה אינו מוקצה לשום משתנה, ומכאן שהוא כשיר לאיסוף אשפה כאשר ה- System.gc מתבצעת שיטה.

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

כאשר אנו מבצעים את הבדיקה שסופקה, הודפסה על המסוף על הקורא המאגר נסגר בגמר המסוף. זה מרמז על לְסַכֵּם שיטה נקראה וזה ניקה את המשאב.

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

בחלק הבא, נראה מדוע יש להימנע משימוש בהם.

3. הימנעות מסופי גמר

למרות היתרונות שהם מביאים, הגמר הגמר מגיע עם חסרונות רבים.

3.1. חסרונות המסיימים

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

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

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

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

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

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

3.2. הדגמת השפעות הגמר

הגיע הזמן לשים את התיאוריה בצד ולראות את ההשפעות של מסיימי הגמר בפועל.

בואו נגדיר מחלקה חדשה עם גמר שאינו ריק:

class class CrashedFinalizable {public static void main (String [] args) זורק ReflectiveOperationException {for (int i = 0;; i ++) {new CrashedFinalizable (); // קוד אחר}} ריקה מוגנת @Override לסיים () {System.out.print (""); }}

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

בתוך ה רָאשִׁי שיטה, חדשה ניתוק לסוף מופע נוצר בכל איטרציה של ה- ל לוּלָאָה. מופע זה אינו מוקצה לשום משתנה, ולכן הוא זכאי לאיסוף אשפה.

בואו נוסיף כמה הצהרות בשורה המסומנת ב // קוד אחר כדי לראות כמה אובייקטים קיימים בזיכרון בזמן ריצה:

אם ((i% 1_000_000) == 0) {Class finalizerClass = Class.forName ("java.lang.ref.Finalizer"); שדה queueStaticField = finalizerClass.getDeclaredField ("תור"); queueStaticField.setAccessible (נכון); ReferenceQueue referenceQueue = (ReferenceQueue) queueStaticField.get (null); שדה queueLengthField = ReferenceQueue.class.getDeclaredField ("queueLength"); queueLengthField.setAccessible (נכון); long queueLength = (long) queueLengthField.get (referenceQueue); System.out.format ("יש% d הפניות בתור% n", queueLength); }

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

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

... יש 21914844 הפניות בתור יש 22858923 הפניות בתור יש 24202629 הפניות בתור יש 24621725 הפניות בתור יש 25410983 הפניות בתור יש 26231621 הפניות בתור יש 26975913 הפניות בתור התור חריג בחוט "הראשי" java.lang.OutOfMemoryError: חריגה ממגבלת התקורה של GC ב- java.lang.ref.Finalizer.register (Finalizer.java:91) ב- java.lang.Object. (Object.java:37) בשעה com.baeldung.finalize.CrashedFinalizable. (CrashedFinalizable.java:6) at com.baeldung.finalize.CrashedFinalizable.main (CrashedFinalizable.java:9) התהליך הסתיים עם קוד יציאה 1

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

אם היינו מסירים את הגמר, מספר הפניות בדרך כלל יהיה 0 והתוכנית תמשיך לפעול לנצח.

3.3. הֶסבֵּר

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

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

אנו יכולים לגשת לתור זה דרך השדה הסטטי תוֹר בתוך ה java.lang.ref.Finalizer מעמד.

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

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

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

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

4. דוגמה ללא גמר

בואו נבדוק פתרון המספק את אותה פונקציונליות אך ללא שימוש ב לְסַכֵּם() שיטה. שים לב שהדוגמה שלמטה אינה הדרך היחידה להחליף את הגמר.

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

הנה ההצהרה על הכיתה החדשה שלנו:

מחלקה ציבורית CloseableResource מיישם את AutoCloseable {private BufferedReader reader; ציבור CloseableResource () {InputStream קלט = this.getClass () .getClassLoader () .getResourceAsStream ("file.txt"); קורא = BufferedReader חדש (InputStreamReader חדש (קלט)); } מחרוזת ציבורית readFirstLine () זורקת IOException {String firstLine = reader.readLine (); חזור firstLine; } @ ביטול ציבורי ריק ריק () {נסה {reader.close (); System.out.println ("Closed BufferedReader בשיטה הקרובה"); } לתפוס (IOException e) {// לטפל בחריג}}}

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

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

להלן שיטת בדיקה הקוראת קובץ קלט ומשחררת את המשאב לאחר סיום עבודתו:

@Test הציבור מבוטל כאשר TryWResourcesExits_thenResourceClosed () זורק IOException {try (CloseableResource resource = new CloseableResource ()) {String firstLine = resource.readFirstLine (); assertEquals ("baeldung.com", firstLine); }}

במבחן הנ"ל, א סגור משאבים מופע נוצר ב לְנַסוֹת חסימה של הצהרת ניסיון-עם-משאבים, ומכאן שמשאב זה נסגר אוטומטית כאשר חסימת הניסיון-משאבים מסיימת את הביצוע.

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

5. סיכום

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

נקודה קריטית אחת שיש לשים לב אליה היא לְסַכֵּם הוצא משימוש החל מג'אווה 9 - ובסופו של דבר יוסר.

כמו תמיד, ניתן למצוא את קוד המקור למדריך זה ב- GitHub.