CodeGym /בלוג Java /Random-HE /הפניות לפנטום ב-Java
John Squirrels
רָמָה
San Francisco

הפניות לפנטום ב-Java

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

    Cat cat = new Cat()

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

  2. SoftReference (הפניה רכה). היה לנו שיעור על התייחסויות כאלה.

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

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

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

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

אתה זוכר את השיטות האלה מהשיעורים על SoftReference ו- WeakReference . חשוב לזכור שהם עובדים אחרת עם סוגים שונים של הפניות. היום לא נצלול לשלושת הסוגים הראשונים. במקום זאת, נדבר על הפניות לפנטום. ניגע בסוגי הפניות האחרים, אך רק בהתייחס לאופן שבו הם שונים מהפניות פנטום. בוא נלך! :) מלכתחילה, למה אנחנו צריכים הפניות פנטום בכלל? כפי שאתה יודע, אוסף האשפה (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("The finalize method has been called on the TestClass object");
   }
}
כאשר אנו יוצרים אובייקטים, בכוונה ניתן להם "עומס" כבד (על ידי הוספת 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("Cleaning up a phantom reference! Removing an object from memory!");
       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("The thread monitoring the queue has started!");
       Reference ref = null;

       // Wait until the references appear in the queue
       while ((ref = referenceQueue.poll()) == null) {

           try {
               Thread.sleep(50);
           }

           catch (InterruptedException e) {
               throw new RuntimeException("Thread " + getName() + " was interrupted!");
           }
       }

       // As soon as a phantom reference appears in the queue, clean it up
       ((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("Collecting garbage!");

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

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

       Thread.sleep(5000);

       System.out.println("Collecting garbage!");

       System.gc();
   }
}
פלט מסוף:

ref = MyPhantomReference@4554617c 
The thread monitoring the queue has started! 
Collecting garbage! 
The finalize method has been called on the TestClass object 
ref = MyPhantomReference@4554617c 
Collecting garbage! 
Cleaning up a phantom reference! 
Removing an object from memory! 
מה אנחנו רואים כאן? הכל קרה כפי שתכננו! שיטת 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