۲۹ شهریور ۱۴۰۳

Techboy

اخبار و اطلاعات روز تکنولوژی

چه مشکلی با Java’s sun.misc.Unsafe وجود دارد؟

بسیاری از بخش‌های کلاس Unsafe جاوا منسوخ شده و با ویژگی‌های جدیدتری جایگزین می‌شوند که عملکرد مشابهی را ارائه می‌دهند. در اینجا چیزی است که شما باید بدانید.

بسیاری از بخش‌های کلاس Unsafe جاوا منسوخ شده و با ویژگی‌های جدیدتری جایگزین می‌شوند که عملکرد مشابهی را ارائه می‌دهند. در اینجا چیزی است که شما باید بدانید.

کلاس sun.misc.Unsafe جاوا از سال ۲۰۰۲ مورد استفاده قرار گرفته است. این کلاس روش‌های سطح پایین ضروری را ارائه می‌کند که توسعه‌دهندگان چارچوب برای ارائه ویژگی‌ها و عملکرد غیرقابل دستیابی استفاده می‌کنند. متأسفانه، ناامن همچنین دارای مشکلات طولانی مدت مربوط به قابلیت نگهداری JVM است. و همانطور که از نام آن پیداست، استفاده از آن کاملاً ایمن نیست. یک JEP جدیدتر پیشنهاد می‌کند که روش‌های دسترسی به حافظه sun.misc.Unsafe را در نسخه آتی جاوا حذف کنید. اما چه چیزی جایگزین آنها خواهد شد؟

این مقاله به کلاس ناامن می‌پردازد، چرا برخی از روش‌های آن حذف می‌شوند، و توسعه‌دهندگان برای آماده شدن برای این تغییر چه کاری می‌توانند انجام دهند.

چرا روش‌های ناامن جاوا منسوخ می‌شوند

کلاس sun.misc.Unsafe جاوا کارهای خاصی انجام می دهد که در غیر این صورت مجاز نیستند. قدرت های آن به دو دسته کلی تقسیم می شوند:

  • دسترسی و تخصیص حافظه سطح پایین و اشاره گر
  • ساخت کلاس خارج از وسایل عادی (حتی فراتر از بازتاب)

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

رفع انواع اولیه جاوا

ناامن تنها ویژگی قدیمی جاوا در بلوک خرد کردن نیست – یا حداقل به دلیل تغییرات بزرگ. انواع ابتدایی مانند int حکم “همه چیز یک شی است” را می شکنند. تعمیر آنها یکی از بسیاری از اهداف طراحی آینده نگر است که توسط پروژه والهالا در نظر گرفته شده است.

چرا توسعه دهندگان از غیرایمن استفاده می کنند

ناامن همه چیز بد نیست، به همین دلیل است که برای مدت طولانی نگهداری می شود. راهنمای Sun.misc.Unsafe Baeldung شامل یک نمای کلی از کارهایی است که توسعه دهندگان می توانند با انجام دهند. کلاس ناامن:

  • نمونه سازی کلاس بدون سازنده
  • دستکاری مستقیم فیلدهای کلاس
  • پرتاب کردن استثناهای علامت‌گذاری شده زمانی که توسط محدوده کنترل نمی‌شوند
  • دسترسی مستقیم به عملیات حافظه پشته
  • دسترسی به عملیات مقایسه و تعویض (CAS)
از توسعه دهنده 10x تا تیم 10x

عملیات مقایسه و تعویض جاوا خوب است مثالی از اینکه چرا ما یک کلاس ناامن داریم. CAS یک دستورالعمل در سطح سخت افزار است که امکان دسترسی به حافظه اتمی را فراهم می کند. Atomicity مزایای عملکرد قابل توجهی را به رشته های همزمان می دهد زیرا به ما امکان می دهد حافظه را بدون مسدود کردن تغییر دهیم. به طور سنتی، APIهای استاندارد جاوا نمی‌توانستند از این ویژگی استفاده کنند، زیرا این ویژگی مختص سیستم عامل است.

در اینجا یک قطعه از عملیات مقایسه و تعویض با استفاده از ناامن، از معرفی Oracle به sun.misc.Unsafe:


public final class AtomicCounter implements Counter {
    private static final Unsafe unsafe;
    private static final long valueOffset;

    private volatile int value = 0;

    static {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe"); // (1)
            f.setAccessible(true); // (2)
            unsafe = (Unsafe) f.get(null); // (3)
            valueOffset = unsafe.objectFieldOffset
                (AtomicCounter.class.getDeclaredField("value")); // (4)
        } catch (Exception ex) { throw new Error(ex); }
    }

    @Override
    public int increment() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1; // (5)
    }

    @Override
    public int get() {
        return value;
    }
}

متوجه خواهید شد که دسترسی به ناامن نیز عجیب است. این یک عضو کلاس ایستا است که ابتدا باید با بازتاب (۱) به آن دسترسی پیدا کرد، سپس به صورت دستی روی دسترس (۲) تنظیم کرد و سپس با بازتاب (۳) به آن ارجاع داد.

با در دست داشتن کلاس Unsafe، افست فیلد مقدار را در کلاس AtomicCounter فعلی (۴) دریافت می کنیم. افست به ما می گوید که فیلد در کجای حافظه در رابطه با تخصیص حافظه کلاس قرار دارد. توجه داشته باشید که در اینجا ما با اشاره گرهایی سروکار داریم که ممکن است برای توسعه دهندگان جاوا ناآشنا باشد، اگرچه آنها در زبان های مدیریت حافظه مستقیم مانند C/C++ استاندارد هستند. اشاره‌گرها با قرار دادن مسئولیت دسترسی به حافظه بر عهده توسعه‌دهنده، دسترسی مستقیم به حافظه پشته‌ای را به ما می‌دهند—چیزی که مکانیسم مجموعه زباله جاوا صراحتاً برای جلوگیری از آن طراحی شده است.

برای اجرای دستور CAS، تابع getAndAddInt() (۵) فراخوانی می شود. این به سیستم عامل می‌گوید دستورات اتمی را با استفاده از this به‌عنوان چارچوب مرجع، در مکان valueOffset و با “دلتا” ۱-به معنی عملیات اجرا کند. سیستم باید ۱ افزایش یابد.

بازسازی sun.misc.Unsafe

اکنون طعم استفاده از ناامن را چشیده اید. این مثال همچنین برخی از اشکالات آن را برجسته می کند.

چرا توسعه دهندگان نرم افزار معیارهای DORA را ترجیح می دهند

انجام عملیات حافظه نادرست می‌تواند باعث “خراش سخت” JVM بدون ایجاد استثنا در پلت فرم شود. مدیریت ضعیف حافظه همچنین می‌تواند منجر به نشت حافظه شود که ایجاد آن بسیار آسان است و در برخی از زبان‌ها تشخیص و رفع آن دشوار است. دسترسی مستقیم به حافظه همچنین می‌تواند حفره‌های امنیتی مانند سرریزهای بافر را باز کند. (برای بررسی بیشتر در مورد معایب استفاده از sun.misc.Unsafe، به Stack Overflow مراجعه کنید.)

اما اشکالات ایجاد شده توسط برنامه نویس تنها بخشی از مشکل است. استفاده از ناامن همچنین منجر به کدهای خاص پیاده سازی می شود. این بدان معناست که برنامه‌هایی که از آنها استفاده می‌کنند ممکن است قابل حمل نباشند. همچنین تغییر پیاده سازی های خود در آن مناطق را برای JVM دشوارتر می کند.

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

گزینه های جایگزین sun.misc.Unsafe

توسعه دهندگان جاوا در حال حاضر در حال جایگزینی ویژگی های ناامن با نسخه های استاندارد و کمتر مشکل ساز هستند. بیایید به گزینه های جایگزین نگاه کنیم، که برخی از آنها در جاوا ۹ معرفی شده اند.

VarHandles

دسته‌های متغیر در JEP 193 توضیح داده شده است. این ویژگی یکی از بزرگترین کاربردهای Unsafe را پوشش می‌دهد، که دسترسی مستقیم به فیلدهای روی پشته و دستکاری آن است. خواندن اهداف JEP برای درک اینکه این ویژگی چه می کند ارزش دارد، اما در اینجا خلاصه ای وجود دارد:

  • نباید JVM را در وضعیت حافظه خراب قرار داد.
  • دسترسی به فیلد یک شیء از قوانین دسترسی مشابه با کدهای بایت getfield و putfield پیروی می‌کند، علاوه بر محدودیتی که یک final فیلد یک شی را نمی توان به روز کرد.
  • ویژگی‌های عملکرد آن باید مشابه یا مشابه عملیات‌های sun.misc.Unsafe باشد.
  • API باید بهتر از sun.misc.Unsafe API باشد.

به طور کلی، VarHandles نسخه ایمن تر و بهتر از ویژگی های Unsafe را در اختیار ما قرار می دهد و عملکرد را به خطر نمی اندازد. با مراجعه به Javadoc for AccessModes. متوجه خواهید شد که مقایسه و تبادل همراه با بسیاری از انواع عملیات اولیه دیگر پشتیبانی می شود. 

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

MethodHandles

کلاس MethodHandles (JEP 274) دارای یک Lookup که جایگزین استفاده از بازتاب برای به دست آوردن و تغییر مجوزها در فیلدها (تکنیکی که قبلاً در مثال CAS دیدید). با این کلاس، مجوزها بر اساس دسترسی به کد حاوی به جای تنظیم مستقیم با بازتاب، اعطا می شوند.

API Stack-Walking

Unsafe.getCallerClass() تا حدی با Stack-Walking API ارائه شده در JEP 259. این یک راه دوربرگردان ایمن تر، اما بیشتر برای دسترسی به کلاس تماس گیرنده ارائه می دهد.

راه حل ها هنوز مورد نیاز است

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

همچنین جالب است که ببینید پروژه های دنیای واقعی چگونه با این موضوع دست و پنجه نرم می کنند. برای مثال، نحوه برخورد پروژه Objenesis Git را در نظر بگیرید. href=”https://github.com/easymock/objenesis/issues/61″ rel=”nofollow”>Unsafe.defineClass() در حال حذف از API.

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

نتیجه گیری

حلقه برنجی برای سفر ناامن جاوا این است که به طور کامل همه ویژگی‌های دسترسی به حافظه ناامن را با نسخه‌های مجاز جایگزین کند. همانطور که می بینید، این کار همه در یک مکان اتفاق نمی افتد. قطعات و قطعات از طریق APIهای جدیدتر اضافه خواهند شد، اگرچه VarHandle به بخش بزرگی از کار می پردازد.

داشتن نسخه‌های جدید عملکرد تنها نیمی از کار است. چارچوب‌ها و پروژه‌های مختلفی که در حال حاضر به روش‌های ناامن متکی هستند باید به گزینه‌های جدید مهاجرت کنند، که به هیچ وجه ساده نیست. در برخی موارد، به بازسازی جدی نیاز دارد.

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