CodeGym /وبلاگ جاوا /Random-FA /ارجاعات فانتوم در جاوا
John Squirrels
مرحله
San Francisco

ارجاعات فانتوم در جاوا

در گروه منتشر شد
سلام! در بحث امروز، ما به طور مفصل در مورد "ارجاعات فانتوم" (PhantomReference) در جاوا صحبت خواهیم کرد. اینها چه نوع مراجعی هستند؟ چرا به آنها "ارجاعات فانتوم" می گویند؟ چگونه مورد استفاده قرار می گیرند؟ همانطور که به یاد دارید، جاوا دارای 4 نوع مرجع است:
  1. StrongReference (ارجاعات معمولی که هنگام ایجاد یک شی ایجاد می کنیم):

    Cat cat = new Cat()

    در این مثال، گربه یک مرجع قوی است.

  2. SoftReference (مرجع نرم). ما در مورد چنین مراجعی درس داشتیم.

  3. مرجع ضعیف (مرجع ضعیف). در اینجا نیز درسی در مورد آنها وجود داشت .

  4. PhantomReference (مرجع فانتوم).

سه مورد آخر انواع عمومی با پارامترهای نوع هستند (به عنوان مثال، SoftReference<Integer> ، WeakReference<MyClass> ). کلاس های SoftReference ، WeakReference و PhantomReference از کلاس Reference به ارث برده می شوند . مهمترین روش های کار با این کلاس ها عبارتند از:
  • get() - شی ارجاع شده را برمی گرداند.

  • clear() - ارجاع به شی را حذف می کند.

این روش ها را از درس های SoftReference و WeakReference به یاد دارید . مهم است که به یاد داشته باشید که آنها با انواع مختلف مراجع به طور متفاوتی کار می کنند. امروز ما به سه نوع اول نمی پردازیم. در عوض، ما در مورد ارجاعات فانتوم صحبت خواهیم کرد. ما به انواع دیگر ارجاعات اشاره خواهیم کرد، اما فقط با توجه به تفاوت آنها با ارجاعات فانتوم. بیا بریم! :) برای شروع، اصلاً چرا به ارجاعات فانتوم نیاز داریم؟ همانطور که می دانید، زباله جمع کننده (gc) حافظه مورد استفاده توسط اشیاء غیر ضروری جاوا را آزاد می کند. جمع کننده یک شی را در دو "گذر" حذف می کند. در اولین گذر، فقط به اشیا نگاه می کند و در صورت لزوم آنها را به عنوان "غیر ضروری" (به معنی "حذف شدن") علامت گذاری می کند. اگر 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