היי! בדיון של היום, נדבר בפירוט על "הפניות פנטום" (PhantomReference) בג'אווה. איזה סוג של הפניות אלו? למה הם נקראים "הפניות פנטום"? איך משתמשים בהם? כזכור, לג'אווה יש 4 סוגים של הפניות:
-
StrongReference (הפניות רגילות שאנו יוצרים בעת יצירת אובייקט):
Cat cat = new Cat()
בדוגמה זו, חתול הוא התייחסות חזקה.
-
SoftReference (הפניה רכה). היה לנו שיעור על התייחסויות כאלה.
-
WeakReference (התייחסות חלשה). היה גם שיעור עליהם כאן .
-
PhantomReference (הפניה לפנטום).
-
get() - מחזיר את האובייקט שאליו מתייחסים;
- clear() - מסיר את ההפניה לאובייקט.
finalize()
השיטה נדחתה עבור האובייקט, היא נקראת. או אולי זה לא נקרא - הכל תלוי רק אם יש לך מזל. אתם בטח זוכרים שזה finalize()
הפכפך :) במעבר השני של אספן האשפה, החפץ נמחק והזיכרון משתחרר. התנהגותו הבלתי צפויה של אספן האשפה יוצרת אצלנו מספר בעיות. אנחנו לא יודעים מתי בדיוק יתחיל אוסף האשפה לרוץ. אנחנו לא יודעים אם finalize()
השיטה תיקרא. בנוסף, ניתן ליצור התייחסות חזקה לאובייקט בזמן finalize()
ביצוע השיטה שלו, ובמקרה זה האובייקט לא יימחק כלל. עבור תוכניות הדורשות דרישות כבדות לזיכרון זמין, זה יכול בקלות להוביל ל- OutOfMemoryError
. כל זה דוחף אותנו להשתמש בהפניות פנטום . העובדה היא שזה משנה את התנהגותו של אספן האשפה. אם לאובייקט יש רק הפניות פנטום, אז:
-
שיטת finalize() שלה נקראת (אם היא נדחפת)
-
אם שום דבר לא משתנה לאחר ששיטת finalize() מסתיימת ועדיין ניתן למחוק את האובייקט, אז הפניה הפאנטום לאובייקט ממוקמת בתור מיוחד: ReferenceQueue .
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 עבור הפניות הפאנטום. שלישית, הפניות פנטום משמשות בעיקר בהליכים מסובכים למחיקת אובייקטים מהזיכרון. זהו זה! :) בכך מסתיים השיעור שלנו היום. אבל אתה לא יכול להגיע רחוק רק בתיאוריה, אז הגיע הזמן לחזור לפתרון משימות! :)
GO TO FULL VERSION