مقدمة
يتم استخدام الفئة غير الآمنة في فئات متعددة من رمز مصدر JDK. يوفر هذا الفئة بعض الوظائف الأساسية لتجاوز JVM ، ويمكن أن يحسن تنفيذه الكفاءة. ومع ذلك ، فهو سيف ذو حدين: كما تنبأ اسمه ، فإنه غير آمن ، والذاكرة التي يخصصها يجب أن تكون مجانية يدويًا (لا يتم إعادة تدويرها بواسطة GC). يوفر فئة غير آمنة بديلاً بسيطًا لميزات معينة من JNI: ضمان الكفاءة مع تسهيل الأمور.
ينتمي هذا الفصل إلى الفصل في الشمس.
تدور هذه المقالة بشكل أساسي حول تجميع وترجمة المقالات التالية.
http://mishadoff.com/blog/java-magic-bart-4-sun-dot-misc-dot-unsafe/
1. معظم طرق واجهة برمجة التطبيقات غير الآمنة هي التطبيقات الأصلية ، والتي تتكون من 105 طريقة ، بما في ذلك الفئات التالية:
(1) المعلومات المتعلقة. إرجاع بعض معلومات الذاكرة ذات المستوى المنخفض: advansize () ، pagesize ()
(2) الكائنات ذات الصلة. قم بتوفير الكائن بشكل أساسي وطرق معالجة المجال: تخصيص () ، ObjectFieldOffset ()
(3) الفئة ذات الصلة. توفير بشكل أساسي أساليب التلاعب بالطبقة والثابتة: staticfieldoffset () ، intereclass () ، infereneanymousclass () ، insureclassinitialized ()
(4) المصفوفات ذات الصلة. طريقة معالجة الصفيف: ArrayBaseOffset () ، ArrayIndexScale ()
(5) متزامن. توفير بشكل أساسي بدائيات التزامن منخفض المستوى (مثل CAS المستندة إلى وحدة المعالجة المركزية (مقارنة و swap)): مراقبة () ، trymonitorenter () ، monitorexit () ، compareandswapint () ، putordered ()
(6) متعلقة بالذاكرة. طريقة وصول الذاكرة المباشرة (تجاوز كومة JVM ومعالجة الذاكرة المحلية مباشرة): altinocatememory () ، copymemory () ، freememory () ، getaddress () ، getint () ، putint ()
2. الحصول على مثيل فئة غير آمن
لا يتم توفير تصميم فئة غير آمن إلا لعملية تشغيل فئة بدء التشغيل الموثوق بها JVM ، وهي فئة نمطية نموذجية. طريقة اكتساب مثيلها هي كما يلي:
static public unsafe getUnsafe () {class cc = sun.reflect.reflection.getCallerClass (2) ؛ إذا كان (cc.getClassLoader ()! = null) يرمي SecurityException ("غير آمن") ؛ إرجاع theunsafe ؛}سيقوم محمل الفئة غير المبدع بالاتصال مباشرة بالطريقة غير الآمن.
هناك حلان. أحدهما هو تحديد الفئة المراد استخدامها كفئة بدء تشغيل من خلال معلمة JVM - XbootClassPath. الطريقة الأخرى هي انعكاس جافا.
الحقل f = unfafe.class.getDeclaredField ("theunsafe") ؛ f.setAccessible (صواب) ؛ غير آمن = (غير آمن) f.get (null) ؛من خلال الإعداد بوحشية في متناول مثيل Singleton الخاص ، ثم الحصول مباشرة على كائن يلقي إلى غير آمن من خلال طريقة Get Field. في IDE ، سيتم تمييز هذه الطرق كخطأ ويمكن حلها من خلال الإعدادات التالية:
التفضيلات -> java -> برنامج التحويل البرمجي -> الأخطاء/التحذيرات -> API المقيدة والمقيدة -> مرجع محظور -> تحذير
3. سيناريوهات التطبيق "المثيرة للاهتمام" لفئة غير آمنة
(1) تجاوز طريقة تهيئة الفصل. تصبح طريقة التخصيص () مفيدة للغاية عندما تريد تجاوز منشئات الكائنات أو المدققات الأمنية أو المُنشئين دون عام.
الفئة A {private long a ؛ // لم تتم تهيئة القيمة العامة a () {this.a = 1 ؛ // التهيئة} public long a () {return this.a ؛ }}فيما يلي مقارنة بين طريقة البناء وطريقة الانعكاس وتخصيصها ()
a o1 = جديد a () ؛ // constructoro1.a () ؛ // prints 1 a o2 = a.class.newinstance () ؛ // Reflectiono2.a () ؛ // prints 1 a o3 = (a) unfafe.allocateinstance (A.Class) ؛ // unfafeo3.a () ؛ // المطبوعات 0
لا يدخل تخصيص TIMINOCTINSTANCE () طريقة المنشئ على الإطلاق ، وفي وضع Singleton يبدو أننا نرى أزمة.
(2) تعديل الذاكرة
تعديل الذاكرة شائع نسبيًا في اللغة C. في Java ، يمكن استخدامه لتجاوز المدقق الأمني.
النظر في قواعد فحص الوصول البسيطة التالية:
Class Guard {private int access_allowed = 1 ؛ Boolean GiveAccess () {return 42 == Access_allowed ؛ }}في ظل الظروف العادية ، يعيد GiveAccess دائمًا كاذبًا ، لكنه لا يحدث دائمًا
Guard Guard = New Guard () ؛ Guard.GiveAccess () ؛ // false ، لا وصول // bypassunsafe غير آمن = getUnsafe () ؛ الحقل f = guard.getclass (). // memory فساد Guard.giveAccess () ؛ // True ، منحت الوصول
عن طريق حساب إزاحة الذاكرة واستخدام طريقة putInt () ، يتم تعديل Access_allowed للفئة. عندما يكون بنية الفصل معروفة ، يمكن دائمًا حساب إزاحة البيانات (بما يتوافق مع حساب إزاحة البيانات في الفئة في C ++).
(3) تنفيذ وظيفة SizeOF () مماثلة للغة C
قم بتنفيذ دالة sizeof () تشبه C عن طريق الجمع بين انعكاس Java ودالة ObjectFieldOffset ().
SizeOf static static static (كائن O) {Unfafe u = getUnsafe () ؛ Hashset Fields = new Hashset () ؛ الفئة C = O.GetClass () ؛ بينما (c! = object.class) {for (field f: c.getDeclaredFields ()) {if ((f.getModifiers () & modifier.static) == 0) {fields.add (f) ؛ }} c = c.getSuperClass () ؛ } // الحصول على إزاحة طويلة maxSize = 0 ؛ لـ (Field F: Fields) {Long Offset = U.ObjectFieldOffset (F) ؛ if (Offset> maxSize) {maxSize = Offset ؛ }} return ((maxSize/8) + 1) * 8 ؛ // padding}فكرة الخوارزمية واضحة للغاية: ابدأ من الفئة الفرعية الأساسية ، وإخراج المجالات غير المنطقية في حد ذاتها ، وجميع فئاتها الفائقة بدورها ، وضعها في مجموعة من الهاشم (الحسابات المتكررة مرة واحدة فقط ، Java هي ميراث واحد) ، ثم استخدام ObjectFieldoffset () للحصول على أقصى إزاحة ، وأخيراً النظر في المحاذاة.
في JVM 32 بت ، يمكن الحصول على الحجم من خلال قراءة فترة طويلة مع إزاحة ملف الفصل 12.
الحجم الطويل الثابت العام (كائن كائن) {return getunsafe (). getAddress (تطبيع (getunsafe (). getint (كائن ، 4L)) + 12l) ؛}دالة الطبيعية () هي طريقة تحول موقعة int إلى غير موقعة طويلة
طبيعتها الطويلة الثابتة الخاصة (قيمة int) {if (value> = 0) قيمة الإرجاع ؛ إرجاع (0L >>> 32) & value ؛}حجم اثنين من sizeof () المحسوبة هو نفسه. إن تطبيق SizeOF () الأكثر قياسيًا هو استخدام java.lang.instrument ، ومع ذلك ، فإنه يتطلب تحديد معلمة سطر الأوامر -javaagent.
(4) تنفيذ النسخ المتماثل Java الضحل
يتمثل مخطط النسخ المتماثل الضحل القياسي في تنفيذ الواجهة المستنسخة أو وظائف النسخ المتماثل المنفذة بمفرده ، وهي ليست وظائف متعددة الأغراض. من خلال الجمع بين طريقة SizeOF () ، يمكن تحقيق نسخ ضحل.
كائن ثابت ضحل (كائن obj) {long size = sizeof (obj) ؛ بداية طويلة = toaddress (OBJ) ؛ عنوان طويل = getUnsafe (). altinocatememory (الحجم) ؛ getunsafe (). copymemory (ابدأ ، العنوان ، الحجم) ؛ العودة من adaddress (العنوان) ؛}Toaddress التالي () و FromAddress () تحويل الكائن إلى عنوانه والعملية العكسية على التوالي.
Static Long Toaddress (Object OBJ) {Object [] Array = New Object [] {obj} ؛ basoffset long = getunsafe (). arrayBaseOffset (كائن []. الفئة) ؛ إرجاع تطبيع (getUnsafe (). getInt (صفيف ، basoffset)) ؛} كائن ثابت من adaddress (عنوان طويل) {object [] array = new Object [] {null} ؛ basoffset long = getunsafe (). arrayBaseOffset (كائن []. الفئة) ؛ getunsafe (). putlong (Array ، baseoffset ، address) ؛ مجموعة العودة [0] ؛}يمكن تطبيق وظيفة النسخ الضحلة أعلاه على أي كائن Java ، ويتم حساب حجمها ديناميكيًا.
(5) إلغاء كلمات المرور في الذاكرة
يتم تخزين حقول كلمة المرور في السلسلة ، ومع ذلك ، تتم إدارة إعادة تدوير السلسلة بواسطة JVM. الطريقة الأكثر أمانًا هي الكتابة فوق حقل كلمة المرور بعد استخدامه.
field stringValue = string.class.getDeclaredField ("value") ؛ stringValue.setAccible (true) ؛ char [] mem = (char [])(6) التحميل الديناميكي للفئات
الطريقة القياسية لفئات التحميل ديناميكيًا هي class.forname () (عند كتابة برامج JDBC ، أتذكرها بعمق). لا يمكن أيضًا تحميل ملفات فئة Java ديناميكيًا.
byte [] classcontents = getClassContent () ؛ class c = getunsafe (). defereeclass (null ، classcontents ، 0 ، classcontents.length) ؛ C.GetMethod ("A"). استدعاء (C.Newinstance () ، null) ؛ // 1getClassContent () طريقة تقرأ ملف الفئة إلى صفيف بايت. بايت ثابت خاص [] getClassContent () يلقي الاستثناء {file f = ملف جديد ("/home/mishadoff/tmp/a.class") ؛ FileInputStream input = new FileInputStream (F) ؛ byte [] content = new byte [(int) f.length ()] ؛ input.Read (content) ؛ input.close () ؛ إرجاع المحتوى ؛}يمكن تطبيقه في التحميل الديناميكي والوكيل والتقطيع وغيرها من الوظائف.
(7) استثناء اكتشاف الحزمة هو استثناء وقت التشغيل.
getunsafe (). throwException (new ioException ()) ؛
يمكن القيام بذلك عندما لا ترغب في التقاط الاستثناء الذي تم فحصه (غير موصى به).
(8) التسلسل السريع
إن Java Serializable القياسي بطيء للغاية ، كما أنه يجب أن يكون لدى الفصل مُنشئًا عامًا بدون معلمة. من الأفضل أن يكون ذلك خارجيًا ، فهو يحتاج إلى تحديد مخطط للصف لتسلسله. إن مكتبات التسلسل الفعالة الشائعة ، مثل Kryo ، بالاعتماد على مكتبات الطرف الثالث ، ستزيد من استهلاك الذاكرة. يمكنك الحصول على القيمة الفعلية للمجال في الفصل من خلال getInt () و getLong () و getObject () والأساليب الأخرى ، واستمر في المعلومات مثل اسم الفصل إلى الملف معًا. حاول Kryo استخدام غير آمن ، ولكن لا توجد بيانات محددة لتحسين الأداء. (http://code.google.com/p/kryo/issues/detail؟id=75)
(9) تخصيص الذاكرة في كومة غير جافا
سيقوم الجديد باستخدام Java بتخصيص الذاكرة للكائنات في الكومة ، وسيتم إدارة دورة حياة الكائن بواسطة JVM GC.
فئة superarray {private Final Static int byte = 1 ؛ الحجم الطويل الخاص ؛ عنوان طويل خاص ؛ superarray العامة (الحجم الطويل) {this.size = size ؛ العنوان = getUnsafe (). allocateMemory (حجم * بايت) ؛ } مجموعة void العامة (Long I ، Byte value) {getUnsafe (). putbyte (العنوان + i * byte ، value) ؛ } public int get (long idx) {return getUnsafe (). getByte (العنوان + idx * byte) ؛ } الحجم الطويل العام () {حجم الإرجاع ؛ }}لا تقتصر الذاكرة المخصصة لـ UNSAFE بواسطة integer.max_value ، ويتم تخصيصها على الذاكرة غير المتجولة. عند استخدامه ، يجب أن تكون حذراً للغاية: إذا نسيت إعادة تدويره يدويًا ، فسيحدث تسرب الذاكرة ؛ إذا كنت وصولًا غير قانوني للعناوين ، فسوف يتسبب ذلك في تعطل JVM. يمكن استخدامه عندما تحتاج إلى تخصيص مناطق مستمرة كبيرة ، والبرمجة في الوقت الفعلي (لا تتسامح مع زمن انتقال JVM). java.nio يستخدم هذه التكنولوجيا.
(10) طلبات في تزامن جافا
باستخدام Unsafe.CompareAndswap () ، يمكن استخدامه لتنفيذ هياكل بيانات خالية من القفل فعالة.
Class Cascounter تنفس Counter {Private Folatile Long Counter = 0 ؛ خاص غير آمن. إزاحة خاصة طويلة. Public Cascounter () يلقي استثناء {غير آمن = getUnsafe () ؛ الإزاحة = unfafe.objectfieldoffset (cascounter.class.getDeclaredField ("counter")) ؛ } Override public void styrement () {long قبل = counter ؛ بينما (! unfafe.compareanswaplong (هذا ، الإزاحة ، قبل ، قبل + 1)) {قبل = العداد ؛ }} Override Public GetCounter () {Return Counter ؛ }}من خلال الاختبار ، يكون بنية البيانات أعلاه هو نفس كفاءة المتغيرات الذرية Java. تستخدم متغيرات Java Atomic أيضًا طريقة مقارنات Unsafe () ، وستتوافق هذه الطريقة في النهاية مع بدائل وحدة المعالجة المركزية المقابلة ، لذلك فهي فعالة للغاية. فيما يلي حل لتنفيذ hashmap الخالية من القفل (http://www.azulsystems.com/about_us/presentations/lock-free-hash. فكرة هذا الحل هي: تحليل كل ولاية ، إنشاء نسخ ، تعديل النسخ ، استخدام Primitives ، قفلات الدوران). في آلات الخادم العادية (Core <32) ، باستخدام ConcurrentHashMap (قبل JDK8 ، تم تنفيذ قفل الفصل الافتراضي ذو 16 قناة ، وتم تنفيذ concurrenthashmap باستخدام خالية من القفل) بشكل واضح.
لخص
ما سبق هو المحتوى الكامل لهذه المقالة. آمل أن يكون لمحتوى هذه المقالة قيمة مرجعية معينة لدراسة أو عمل الجميع. إذا كان لديك أي أسئلة ، فيمكنك ترك رسالة للتواصل. شكرا لك على دعمك إلى wulin.com.