فهمیدن زمان پاک شدن یک آبجکت از حافظه در جاوا





در جاوا امکان مدیریت مستقیم حافظه توسط برنامه نویس وجود نداره و این وظیفه رو یک ساب سیستمی بنام Garbage Collector برعهده داره . البته متدهایی چون

System.gc();

وجود داره که میتونید باهاش Garbage collector رو صدا بزنید تا عمل پاکسازی حافظه رو انجام بده ولی هیچ تضمینی برای حرف گوش کن بودن اون وجود نداره حتی فراخوانی مستقیم متد finalize باعث نمیشه یک آبجکت به دستور ما از حافظه پاک بشه . کلا در این زمینه دست برنامه نویس برای صادر کردن دستورات مستقیم بسته است ! فرض کنید میخواهیم زمان پاک شدن یک آبجکت از حافظه رو بدونیم تا بعدش یک کاری رو انجام بدیم مثلا فرض کنید میخواهیم یک حافظه کش داشته باشیم که آبجکت رو داخل اون ذخیره کنیم و هر موقع پاک شد دوباره از نو cache ش کنیم چیکار باید بکنیم ؟

یا یک آبجکتی داریم که خیلی بزرگه مثلا یک تصویر خیلی بزرگی هس که میخواهیم ازش فقط یکی در حافظه لود شده باشه و هر موقع پاک شد دوباره یکی از اون بسازیم و توی حافظه لود کنیم چیکار باید بکنیم ؟


جواب این سوال رو اگه بخواهیم با دانش پایه ای جاوا جواب بدهیم و سراغ ابزارهای مدیریت و مانیتورینگ منابع حین اجرا مثل JMX و JConsole نریم این است :

PhantomReference

در واقع PhantomRenference یک ارجاع به آبجکتی است که توسط Garbage Collector از حافظه heap جمع اوری شده و الان داخل یک صف بنام ReferenceQueue قرار داره و منتظره تا ما دستی ( به کمک کد ) اونو حذف کنیم در واقع اثر و ردپای اونو پاک می کنیم . برای اینکه بدونیم مکانیسم انجام اینکار چطور است یک مثال کوچیک کار می کنیم من این تیکه کد رو از گیت هاب برداشتم که متعلق به یک پروژه بود .

public class TestPhantomRef {

    @Test
    public void testPhantomRef() throws Exception {
        SoftCache<String, byte[]> cache = new SoftCache<>();
      1-  byte[] bigImage = new byte[64 * 1024];
        String key1 = &quotbigImage"
     2-   ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
   3-     PhantomReference<byte[]> watcher = new PhantomReference<>(bigImage, queue);
        cache.checkin(key1, bigImage);
        Assert.assertEquals(bigImage, cache.checkout(key1));
        Assert.assertNull(cache.checkout(key1));
        cache.checkin(key1, bigImage);
        int i = 0;
        while (watcher != queue.poll()) {
            String key = &quotkey_&quot + i;
            byte[] value = new byte[64 * 1024];
            cache.checkin(key, value);
            i += 1;
        }
       
        System.out.println(&quotcurrent size of cache: &quot+cache.getCurrentSize());
        Assert.assertNull(cache.checkout(key1));
  
    }
}

ابتدا یک آبجکت بنام bigImage ایجاد می کنیم ( خط شماره یک) فرض می کنیم این یه ابجکتی ست که میخواهیم زمان پاک شدنش از حافظه رو بدونیم . بعد برای اینکه بتونیم زمان پاک شدنش از حافظه رو بدونیم براش یک ارجاع از نوع PhantomReference میسازیم (خط شماره سه ) نحوه ساخت یک PhantomReference اینطوری است که دوتا ورودی می گیرد یکی آبجکتی که میخواهیم زمان پاک شدنش از حافظه رو بدونیم و دیگری یه صف که وقتی Garbage collector اونو حذف کرد میندازه داخل این صف (خط شماره ۲) که بعدا ما بتونیم با چک کردن صف بفهمیم این آبجکت ما مرده و الان جسدش اینجا قرار داره و دست مارو می بوسه تا ببریم رسمی خاکش کنیم . در کد بالا این آبجکت رو میخوایم داخل یک کش ذخیره می کنیم بعد یه مدت زمانی میریم سراغ کارهای دیگه ( مثلا در بدنه حلقه while میاد داخل اون کش آبجکتهای دیگه ای هم ذخیره می کنه و مدام چک می کنه کی چیزی که از صف پول میشه همون ابجکت شبح ما هس به بیان دیگه کی ابجکت ما توسط اشغال جمع از حافظه حذف شده ،به محض اینکه ابجکت شبح رو پول کرد از حلقه میاد بیرون ) .

حالا اگه داخل کش رو بررسی کنیم اثری از اون ابجکت نیس! حال میتونیم مثلا دوباره همون آبجکت رو بسازیم و داخل کش ذخیره کنیم یا هر کار دیگه ای که دلمون میخواد.



ته نوشت : همون طور که میدونید آبجکت ها در داخل حافظه Heap ذخیره می شوند و و برای اینکه برنامه نویس داخل کدش بتونه به اونا دسترسی پیدا کنه و دستکاری شون کنه نیاز به متغیر رفرنس به اونا داره که معمولا این reference variable ها درجای دیگه ای بنام stack ذخیره می شوند در جاوا چند نوع رفرنس به ابجکت ها داریم :

  • Strong reference
  • Soft reference
  • weak reference
  • phantom reference

وقتی به صورت عادی یک آبجکتی رو با عملگر new ایجاد می کنیم یک رفرنس از نوع strong ایجاد می کنیم :


Cat cat=new Cat();

در اینجا رفرنس cat از نوع strong است وقتی میخواهیم موقعی که باخطای outOfMemoryError روبه رو شدیم آشغال جمع کن اول سراغ یک سری خاص از آبجکت ها بره اون موقع آبجکتهایی که فکر می کنیم اگه از حافظه پاک بشن مشکلی برامون ایجاد نمی کنن رو بهشون ارجاعی از نوع Soft میدیم . در واقع آبجکت هایی که ارجاعی از نوع soft داشته باشند فقط موقعی پاک میشوند که هیچ ارجاعی از نوع strong نداشته باشند و نیز حافظه heap کافی برای ایجاد آبجکت جدید موجود نباشد . اگه برای آبجکتی ارجاعی از نوع weak تعریف کنیم اون آبجکت در دور بعدی که آشغال جمع کن اجرا میشه پاکش خواهد کرد .


برای ساخت ارجاعی از نوع soft و... باید از کلاس های پکیج java.lang.ref استفاده کنیم. که خب برای اهداف خاصی طراحی شدند و معمولا توی یه پروژه نرمال چنین ارجاع هایی رو به ندرت خواهیم دید.