ہائے! آج کی بحث میں، ہم جاوا میں "فینٹم ریفرنسز" (PhantomReferences) کے بارے میں تفصیل سے بات کریں گے۔ یہ کس قسم کے حوالہ جات ہیں؟ انہیں "پریتی حوالہ جات" کیوں کہا جاتا ہے؟ وہ کس طرح استعمال ہوتے ہیں؟ جیسا کہ آپ کو یاد ہوگا، جاوا میں 4 قسم کے حوالہ جات ہیں:
-
StrongReference (عام حوالہ جات جو ہم کسی چیز کو بناتے وقت بناتے ہیں):
Cat cat = new Cat()
اس مثال میں، بلی ایک مضبوط حوالہ ہے۔
-
نرم حوالہ (نرم حوالہ)۔ ہمارے پاس ایسے حوالوں کے بارے میں ایک سبق تھا۔
-
کمزور حوالہ (کمزور حوالہ)۔ ان کے بارے میں یہاں ایک سبق بھی تھا ۔
-
PhantomReference (پریتی حوالہ)۔
-
get() - حوالہ شدہ آبجیکٹ واپس کرتا ہے۔
- clear() — آبجیکٹ کا حوالہ ہٹاتا ہے۔
finalize()
طریقہ آبجیکٹ کے لیے اوور رائڈ کر دیا گیا ہے تو اسے کہا جاتا ہے۔ یا شاید اسے نہیں کہا جاتا ہے - یہ سب صرف اس بات پر منحصر ہے کہ آیا آپ خوش قسمت ہیں۔ آپ کو شاید یاد ہوگا کہ یہ finalize()
چکنا چور ہے :) کوڑا اٹھانے والے کے دوسرے پاس میں، اعتراض کو حذف کر دیا جاتا ہے اور میموری کو آزاد کر دیا جاتا ہے۔ کچرا اٹھانے والے کا غیر متوقع رویہ ہمارے لیے کئی مسائل پیدا کرتا ہے۔ ہم نہیں جانتے کہ کچرا اٹھانے والا کب چلنا شروع کرے گا۔ ہمیں نہیں معلوم کہ finalize()
طریقہ کار کہا جائے گا یا نہیں۔ اس کے علاوہ، کسی چیز کا ایک مضبوط حوالہ تخلیق کیا جا سکتا ہے جب اس کے finalize()
طریقہ کار پر عمل کیا جا رہا ہو، ایسی صورت میں آبجیکٹ کو بالکل بھی حذف نہیں کیا جائے گا۔ ایسے پروگراموں کے لیے جو دستیاب میموری پر بھاری مطالبات کرتے ہیں، یہ آسانی سے ایک OutOfMemoryError
. یہ سب ہمیں پریت کے حوالہ جات استعمال کرنے پر مجبور کرتا ہے ۔ حقیقت یہ ہے کہ اس سے کچرا اٹھانے والے کے رویے میں تبدیلی آتی ہے۔ اگر آبجیکٹ میں صرف پریت حوالہ جات ہیں، تو:
-
اس کا حتمی شکل () طریقہ کہا جاتا ہے (اگر یہ اوور رائڈ ہو)
-
اگر فائنلائز() طریقہ ختم ہونے کے بعد کچھ بھی تبدیل نہیں ہوتا ہے اور آبجیکٹ کو اب بھی حذف کیا جا سکتا ہے، تو آبجیکٹ کا فینٹم حوالہ ایک خاص قطار میں رکھا جاتا ہے: 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 سے وراثت میں آئے ۔ ہمیں ایسی کلاس کی ضرورت کیوں ہے؟ یہ سب سیدھا ہے۔ یہ ہمیں واضح () طریقہ کار میں اضافی منطق شامل کرنے کی اجازت دے گا تاکہ اس بات کی تصدیق کی جا سکے کہ فینٹم حوالہ واقعی صاف ہو گیا ہے (جس کا مطلب ہے کہ اعتراض کو حذف کر دیا گیا ہے)۔
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 میں ظاہر ہوں گے ۔ جیسے ہی اس طرح کا حوالہ قطار میں ختم ہوتا ہے، صفائی () طریقہ اس پر کہا جاتا ہے:
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!
ہم یہاں کیا دیکھتے ہیں؟ جیسا کہ ہم نے منصوبہ بنایا تھا سب کچھ ہوا! ہمارے آبجیکٹ کا فائنلائز () طریقہ اوور رائڈ ہے اور اسے اس وقت بلایا گیا جب کوڑا اٹھانے والا چل رہا تھا۔ اگلا، پریت کا حوالہ ReferenceQueue میں ڈال دیا گیا ۔ وہاں رہتے ہوئے، اس کا واضح () طریقہ کہا گیا تھا (جس کے اندر ہم نے کلین اپ () کو کنسول میں آؤٹ پٹ کرنے کے لیے کہا تھا)۔ آخر کار، آبجیکٹ کو میموری سے ڈیلیٹ کر دیا گیا۔ اب آپ دیکھتے ہیں کہ یہ کیسے کام کرتا ہے :) یقیناً، آپ کو فینٹم حوالوں سے متعلق تمام تھیوری کو یاد کرنے کی ضرورت نہیں ہے۔ لیکن یہ اچھا ہو گا اگر آپ کم از کم اہم نکات کو یاد رکھیں۔ سب سے پہلے، یہ سب سے کمزور ترین حوالہ جات ہیں۔ وہ تبھی عمل میں آتے ہیں جب اعتراض کا کوئی دوسرا حوالہ باقی نہ رہے۔ حوالہ جات کی جو فہرست ہم نے اوپر دی ہے اسے مضبوط سے کمزور تک نزولی ترتیب میں ترتیب دیا گیا ہے: 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 () طریقہ نرم اور کمزور حوالوں کے لیے مکمل طور پر عام اشیاء کو واپس کر دیتا ہے، لیکن یہ فینٹم ریفرنس کے لیے کالعدم ہو گیا۔ تیسرا، فینٹم حوالہ جات بنیادی طور پر میموری سے اشیاء کو حذف کرنے کے پیچیدہ طریقہ کار میں استعمال ہوتے ہیں۔ یہی ہے! :) اس سے ہمارا آج کا سبق ختم ہوتا ہے۔ لیکن آپ اکیلے نظریہ پر زیادہ نہیں جا سکتے، لہذا اب وقت آگیا ہے کہ کاموں کو حل کرنے پر واپس جائیں! :)
GO TO FULL VERSION