أهلاً! في مناقشة اليوم، سنتحدث بالتفصيل عن "المراجع الوهمية" (PhantomReference) في Java. أي نوع من المراجع هذه؟ لماذا يطلق عليهم "المراجع الوهمية"؟ كيف يتم استخدامها؟ كما تتذكر، تحتوي Java على أربعة أنواع من المراجع:
-
StrongReference (المراجع العادية التي نقوم بإنشائها عند إنشاء كائن):
Cat cat = new Cat()
في هذا المثال، تعتبر cat مرجعًا قويًا.
-
مرجع ناعم (مرجع ناعم). لقد كان لدينا درس حول مثل هذه المراجع.
-
مرجع ضعيف (مرجع ضعيف). كان هناك أيضًا درس عنهم هنا .
-
المرجع الوهمي (المرجع الوهمي).
-
get() - إرجاع الكائن المشار إليه؛
- واضح () - يزيل الإشارة إلى الكائن.
finalize()
تم تجاوز الطريقة للكائن، فسيتم استدعاؤها. أو ربما لم يتم استدعاؤه — كل هذا يتوقف فقط على ما إذا كنت محظوظًا. ربما تتذكر أن هذا finalize()
أمر متقلب :) في المرور الثاني لأداة تجميع البيانات المهملة، يتم حذف الكائن وتحرير الذاكرة. يخلق السلوك غير المتوقع لجامع البيانات المهملة عددًا من المشكلات بالنسبة لنا. لا نعرف بالضبط متى سيبدأ تشغيل أداة تجميع البيانات المهملة. لا نعرف ما إذا كان finalize()
سيتم استدعاء الطريقة أم لا. بالإضافة إلى ذلك، يمكن إنشاء مرجع قوي للكائن أثناء finalize()
تنفيذ أسلوبه، وفي هذه الحالة لن يتم حذف الكائن على الإطلاق. بالنسبة للبرامج التي تتطلب متطلبات كبيرة على الذاكرة المتوفرة، يمكن أن يؤدي ذلك بسهولة إلى خطأ OutOfMemoryError
. كل هذا يدفعنا إلى استخدام المراجع الوهمية . الحقيقة هي أن هذا يغير سلوك جامع البيانات المهملة. إذا كان الكائن يحتوي على مراجع وهمية فقط، فحينئذٍ:
-
يتم استدعاء أسلوب الإنهاء () الخاص به (إذا تم تجاوزه)
-
إذا لم يتغير شيء بمجرد انتهاء طريقة 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" إلى كل كائن) من أجل استهلاك المزيد من الذاكرة. بالإضافة إلى ذلك، قمنا بتجاوز طريقة الإنهاء () للتأكد من تشغيلها. بعد ذلك، نحتاج إلى فئة ترث من 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();
}
}
بعد ذلك، نحتاج إلى سلسلة رسائل منفصلة تنتظر أداة تجميع البيانات المهملة للقيام بعملها، وستظهر الروابط الوهمية في قائمة الانتظار المرجعية الخاصة بنا . بمجرد أن ينتهي هذا المرجع في قائمة الانتظار، يتم استدعاء طريقة التنظيف () عليه:
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() الخاصة بكائننا وتم استدعاؤها أثناء تشغيل أداة تجميع البيانات المهملة. بعد ذلك، تم وضع المرجع الوهمي في قائمة الانتظار المرجعية . أثناء وجوده هناك، تم استدعاء أسلوبه الواضح () (الذي قمنا من خلاله باستدعاء Cleanup () من أجل الإخراج إلى وحدة التحكم). وأخيراً، تم حذف الكائن من الذاكرة. الآن ترى بالضبط كيف يعمل هذا :) بالطبع، لا تحتاج إلى حفظ كل النظريات المتعلقة بالمراجع الوهمية. ولكن سيكون من الجيد أن تتذكر النقاط الرئيسية على الأقل. أولاً، هذه هي أضعف المراجع على الإطلاق. يتم تشغيلها فقط عندما لا يتم ترك أي إشارات أخرى للكائن. قائمة المراجع التي قدمناها أعلاه مرتبة تنازليًا من الأقوى إلى الأضعف: StrongReference -> SoftReference -> WeakReference -> PhantomReference يدخل المرجع الوهمي المعركة فقط في حالة عدم وجود مراجع قوية أو ضعيفة أو ضعيفة لكائننا: ) ثانيًا، تُرجع طريقة get() دائمًا قيمة فارغة للمرجع الوهمي. فيما يلي مثال بسيط حيث نقوم بإنشاء ثلاثة أنواع مختلفة من المراجع لثلاثة أنواع مختلفة من السيارات:
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() كائنات عادية تمامًا للمراجع الضعيفة والضعيفة، لكنها أعادت قيمة فارغة للمرجع الوهمي. ثالثًا، تُستخدم المراجع الوهمية بشكل أساسي في الإجراءات المعقدة لحذف الكائنات من الذاكرة. هذا كل شيء! :) بهذا نختتم درسنا اليوم. لكن لا يمكنك المضي قدمًا في النظرية وحدها، لذا حان الوقت للعودة إلى حل المهام! :)
GO TO FULL VERSION