JavaRush /בלוג Java /Random-HE /PhantomReference ב-Java

PhantomReference ב-Java

פורסם בקבוצה
שלום! בשיעור של היום, נדבר בפירוט על הפניות לפנטום ב-Java. איזה סוג של קישורים אלה, למה הם נקראים "פנטום" וכיצד להשתמש בהם? כזכור, ישנם 4 סוגי קישורים ב-Java:
  1. StrongReference (הפניות רגילות שאנו יוצרים בעת יצירת אובייקט):

    Cat cat = new Cat()

    cat בדוגמה זו היא התייחסות חזקה.

  2. SoftReference (הפניה רכה). הייתה לנו הרצאה על הקישורים האלה.

  3. WeakReference (התייחסות חלשה). גם עליהם הייתה הרצאה, כאן .

  4. PhantomReference (הפניה לפנטום).

אובייקטים משלושת הסוגים האחרונים מוקלדים (לדוגמה, SoftReference<Integer> , WeakReference<MyClass> ). כיתות SoftReference, WeakReferenceויורשים PhantomReferenceמהכיתה Reference. השיטות החשובות ביותר בעבודה עם שיעורים אלו הן:
  • get()- מחזיר את האובייקט שאליו מתייחס קישור זה;

  • clear()- מוחק הפניה לאובייקט.

אתה זוכר את השיטות האלה מהרצאות על SoftReferenceו WeakReference. חשוב לזכור שהם עובדים אחרת עם סוגים שונים של קישורים. היום לא נשקול בפירוט את שלושת הסוגים הראשונים, אלא נדבר על קישורי פנטום. ניגע גם בסוגים אחרים של קישורים, אבל רק בחלק בו נדבר על ההבדלים מהם קישורי פנטום. ללכת! :) בואו נתחיל מלכתחילה למה אנחנו צריכים קישורי פנטום. כידוע, אספן האשפה (Garbage Collector או gc) אחראי לשחרור זיכרון מחפצי Java מיותרים . האספן מסיר את החפץ בשני "מעברים". במעבר הראשון, הוא מסתכל רק על חפצים, ובמידת הצורך מסמן אותם כ"מיותרים ויש למחוק". אם לאובייקט הזה הייתה שיטה שדרסה finalize(), היא נקראת. או שזה לא נקרא - תלוי במזל שלך. אתם בטח זוכרים שזה finalize()דבר הפכפך :) במעבר השני של האספן, החפץ נמחק והזיכרון משתחרר. התנהגות בלתי צפויה זו של אספן האשפה יוצרת עבורנו מספר בעיות. אנחנו לא יודעים מתי בדיוק יתחיל אוסף האשפה לעבוד. אנחנו לא יודעים אם השיטה תיקרא finalize(). בנוסף, במהלך הפעולה finalize()ניתן ליצור קישור חזק לאובייקט, ואז הוא לא יימחק כלל. במערכות זולות זיכרון, זה יכול בקלות להוביל ל OutOfMemoryError. כל זה דוחף אותנו לשימוש בקישורי פנטום . הנקודה היא שזה משנה את התנהגותו של אוסף האשפה. אם נותרו רק קישורי פנטום לאובייקט, אז יש לו:
  • השיטה נקראת finalize()(אם היא דרסה);

  • אם לאחר העבודה finalize()שום דבר לא השתנה ועדיין ניתן למחוק את האובייקט, הפניה פנטום לאובייקט ממוקמת בתור מיוחד - ReferenceQueue.

הדבר החשוב ביותר שיש להבין כשעובדים עם הפניות פנטום הוא שאובייקט אינו מוסר מהזיכרון כל עוד הפנייה לפנטום שלו נמצאת בתור זה . הוא יימחק רק לאחר קישור הפנטום של clear(). בואו נסתכל על דוגמה. ראשית, בואו ניצור מחלקת בדיקה שתשמור כמה נתונים.
public class TestClass {
   private StringBuffer data;
   public TestClass() {
       this.data = new StringBuffer();
       for (long i = 0; i < 50000000; i++) {
           this.data.append('x');
       }
   }
   @Override
   protected void finalize() {
       System.out.println("У an object TestClass вызван метод finalize!!!");
   }
}
אנו "טוענים" בכוונה אובייקטים בנתונים בעת יצירתם (הוספת 50 מיליון תווים "x" לכל אובייקט) על מנת לתפוס יותר זיכרון. בנוסף, אנו עוקפים באופן ספציפי את השיטה finalize()כדי לראות שהיא עבדה. בשלב הבא אנחנו צריכים מחלקה שתירש מ- PhantomReference. למה אנחנו צריכים שיעור כזה? זה פשוט. כך נוכל להוסיף היגיון נוסף לשיטה clear()כדי לראות שהפניית הפנטום אכן נוקתה (ולכן האובייקט נמחק).
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class MyPhantomReference<TestClass> extends PhantomReference<TestClass> {

   public MyPhantomReference(TestClass obj, ReferenceQueue<TestClass> queue) {

       super(obj, queue);

       Thread thread = new QueueReadingThread<TestClass>(queue);

       thread.start();
   }

   public void cleanup() {
       System.out.println("Очистка фантомной ссылки! Удаление an object из памяти!");
       clear();
   }
}
לאחר מכן, נצטרך שרשור נפרד שיחכה לאספן האשפה שיעשה את שלו, ובתור ReferenceQueueשלנו יופיעו קישורי פנטום. ברגע שקישור כזה ייכנס לתור, השיטה שלו תיקרא cleanup():
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;

public class QueueReadingThread<TestClass> extends Thread {

   private ReferenceQueue<TestClass> referenceQueue;

   public QueueReadingThread(ReferenceQueue<TestClass> referenceQueue) {
       this.referenceQueue = referenceQueue;
   }

   @Override
   public void run() {

       System.out.println("Поток, отслеживающий очередь, стартовал!");
       Reference ref = null;

       //ждем, пока в очереди появятся ссылки
       while ((ref = referenceQueue.poll()) == null) {

           try {
               Thread.sleep(50);
           }

           catch (InterruptedException e) {
               throw new RuntimeException("Поток " + getName() + " был прерван!");
           }
       }

       //How только в очереди появилась фантомная link - очистить ее
       ((MyPhantomReference) ref).cleanup();
   }
}
ולבסוף, אנחנו צריכים שיטה main(): בואו נשים אותה במחלקה נפרדת Main. בו ניצור אובייקט TestClass, התייחסות פנטום אליו ותור להפניות פנטום. אחרי זה נתקשר לאספן האשפה ונראה מה קורה :)
import java.lang.ref.*;

public class Main {

   public static void main(String[] args) throws InterruptedException {
       Thread.sleep(10000);

       ReferenceQueue<TestClass> queue = new ReferenceQueue<>();
       Reference ref = new MyPhantomReference<>(new TestClass(), queue);

       System.out.println("ref = " + ref);

       Thread.sleep(5000);

       System.out.println("Вызывается сборка мусора!");

       System.gc();
       Thread.sleep(300);

       System.out.println("ref = " + ref);

       Thread.sleep(5000);

       System.out.println("Вызывается сборка мусора!");

       System.gc();
   }
}
פלט מסוף: ref = MyPhantomReference@4554617c השרשור המנטר את התור התחיל! איסוף אשפה נקרא! שיטת finalize נקראה על האובייקט TestClass!!! ref = MyPhantomReference@4554617c קוראים לאיסוף זבל! מנקה את קישור הפנטום! הסרת חפץ מהזיכרון! מה אנחנו רואים כאן? הכל קרה כפי שתכננו! למחלקת האובייקטים שלנו הייתה שיטה שדרסה finalize()והיא נקראה בזמן שהאספן פעל. לאחר מכן, קישור הפנטום הוצב בתור ReferenceQueue. שם הייתה לה שיטה שנקראת clear()(ממנה עשינו cleanup()להוסיף פלט לקונסולה). כתוצאה מכך, האובייקט הוסר מהזיכרון. עכשיו אתה רואה בדיוק איך זה עובד :) כמובן, אתה לא צריך לשנן את כל התיאוריות הקשורות לקישורי פנטום. אבל זה יהיה טוב אם תזכור לפחות את הנקודות העיקריות. ראשית , אלו הם החוליות החלשות מכולן. הם נכנסים לפעולה רק כאשר אין התייחסויות אחרות לאובייקט. רשימת הקישורים שנתנו לעיל היא בסדר יורד של חוזק: StrongReference-> SoftReference-> WeakReference-> PhantomReference קישור הפנטום ייכנס לקרב רק כאשר אין קישורים חזקים, רכים או חלשים על האובייקט שלנו :) שנית , השיטה get()עבור קישור פנטום תמיד חוזר null. הנה דוגמה פשוטה שבה אנו יוצרים שלושה סוגים שונים של קישורים עבור שלושה סוגים שונים של מכוניות:
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;

public class Main {

   public static void main(String[] args) {

       Sedan sedan = new Sedan();
       HybridAuto hybrid = new HybridAuto();
       F1Car f1car = new F1Car();

       SoftReference<Sedan> softReference = new SoftReference<>(sedan);
       System.out.println(softReference.get());

       WeakReference<HybridAuto> weakReference = new WeakReference<>(hybrid);
       System.out.println(weakReference.get());

       ReferenceQueue<F1Car> referenceQueue = new ReferenceQueue<>();

       PhantomReference<F1Car> phantomReference = new PhantomReference<>(f1car, referenceQueue);
       System.out.println(phantomReference.get());

   }
}
פלט קונסולה: Sedan@4554617c HybridAuto@74a14482 null השיטה get()החזירה אובייקטים נורמליים למדי עבור הקישור הרך והחוליה החלשה, אך חזרה nullעבור הפנטום. שלישית , תחום השימוש העיקרי בהפניות פנטום הוא הליכים מורכבים להסרת אובייקטים מהזיכרון. זה הכל! :) זה מסיים את השיעור שלנו להיום. אבל תיאוריה לבדה לא תביא אותך רחוק, אז הגיע הזמן לחזור לפתרון בעיות! :)
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION