پوینتر سرگردان (dangling pointer) در جاوا

این مطلب خلاصه‌ای از جست و جو های من در باب کار با حافظه در جاوا و نحوه ساخت پوینتر سرگردان در این زبان است که فکر می‌کنیم به خاطر رفرنس‌ها و گاربیج کالکتور، قرار نیست پوینتر سرگردان داشته باشد.

خب باید اشاره کنم گمان شما درست است و با استفاده‌های معمولی از امکانات جاوا به پوینتر و پوینتر سرگردان بر نمی‌خورید ولی با استفاده از ویژگی‌های داخل sun.misc.unsafe می‌توان دسترسی مستقیم به حافظه داشت.

در این نوشته می‌خواهیم علاوه بر مرور متد های دسترسی مستقیم به حافظه(با استفاده از unsafe)، اتفاقات بعد از سرگردان شدن پوینتر را نیز بررسی کنیم.

قدم اول import کردن پکیج های مورد نیاز است. برای اینکار java.lang.reflect.Field و sun.misc.Unsafe را import می‌کنیم.

import java.lang.reflect.Field;

import sun.misc.Unsafe;

توجه: با صرف import کردن این پکیج‌ها، جاوا به شما warning استفاده از porperty های داخلی و احتمال حذف شدن در نسخه‌های آتی را می‌دهد.

قدم بعدی در قسمت main برنامه دستورات زیر را برای ساخت یک Unsafe (کلاسی با متدهایی برای استفاده از حافظه) می‌نویسیم:

Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");

field.setAccessible(true);

Unsafe unsafe = (sun.misc.Unsafe) field.get(null);

خب تا این مرحله unsafe ما ساخته شد، در مرحله بعد درخواست تخصیص حافظه با دستور allocate می‌دهیم و آدرس حافظه‌ی تخصیص داده شده را در یک long با نام memoryAddress می‌ریزیم (در اینجا تعداد بایت های درخواست شده به اندازه یک long است):

long memoryAddress = unsafe.allocateMemory(Long.SIZE);

با دستور putAddress می توانیم در یک آدرس، مقداری را بریزیم، مشابه زیر(مقداد عددی ۱ را در حافظه خود ریختیم):

unsafe.putAddress(memoryAddress,1);

در نهایت با دستور unsafe.getAddress و ورودی دادن خانه حافظه می توانیم مقدار آن خانه را خروجی بگیریم:

System.out.println("value of "+memoryAddress+ " is: "+unsafe.getAddress(memoryAddress));

در نهایت با دستور freeMemory حافظه تخصیص‌یافته را آزاد کنیم.

unsafe.freeMemory(memoryAddress);

تا اینجا یک خانه حافظه درخواست کردیم و مقدار مورد نظر خود را داخلش ریختیم و از روی آن خواندیم. این کارها بدون مشکل حاد و خطا خواهد بود و صرفا به چندین warning منجر خواهد شد.

توجه: برای استفاده از Unsafe باید کد مورد نظر خود را حتما داخل try/catch قرار دهید زیرا جاوا متد های Unsafe را مستعد run-time error می‌داند.

در ادامه می‌خواهیم پوینتر سرگردان بسازیم و رفتار آن را بررسی کنیم:

پس از آزاد کردن حافظه هنوز آدرس آن را در اختیار داریم و می‌توانیم همچنان از putAddress و getAddress استفاده کنیم:

unsafe.putAddress(memoryAddress, 2);

long dangValue = unsafe.getAddress(memoryAddress);

System.out.println("this is danling value : " + dangValue);

این‌بار مقدار ۲ را در مرجع پوینتر(که اکنون سرگردان است) ریختیم، سپس مقدار آن خانه را در dangValue خروجی گرفتیم و چاپ کردیم.

این کار منجر به بروز fatal error خواهد شد و کد ما وارد catch می‌شود ولی نکته جالب اینجاست که مقدار آن خانه حافظه به ۲ تغییر می‌کند(اگرچه قانونا دسترسی به آن نداریم)

نتیجه اجرای کدهای بالا :

value of 140234709635504 is: 1

memory address 140234709635504 is dangling now

this is danling value : 2

# A fatal error has been detected by the Java Runtime Environment:

همانطور که میبینید مقدار dangValue برابر ۲ قرار گرفته و در خط سوم خروجی چاپ شده‌است و پس از آن برنامه stacktrace را چاپ کرده است(دستور داخل catch)

همانطورکه ذکر شد نکته عجیب اینجاست که با وجود بروز خطا، برنامه به کار خود ادامه داده و مقدار حافظه سرگردان را ۲ قرار می‌دهد!

کل کد به صورت زیر است:

و خروجی برنامه در ترمینال به صورت زیر است :

متن کد ها در این ریپازیتوری در دسترس است:

https://github.com/rsharifnasab/java_pointer

منبع کدها (فایل ارجینال) از این سایت دریافت گردید :

https://www.mkyong.com/java/java-write-directly-to-memory/

مثل همیشه از شنیدن نظرات شما استقبال می‌کنم

شاد باشید و خندون :)