JavaRush /مدونة جافا /Random-AR /كيف يتم تحميل الفئات في JVM
Aleksandr Zimin
مستوى
Санкт-Петербург

كيف يتم تحميل الفئات في JVM

نشرت في المجموعة
بعد الانتهاء من الجزء الأصعب من عمل المبرمج وكتابة تطبيق "Hello World 2.0"، كل ما تبقى هو تجميع مجموعة التوزيع ونقلها إلى العميل، أو على الأقل إلى خدمة الاختبار. في التوزيع، كل شيء كما ينبغي أن يكون، وعندما نطلق برنامجنا، تظهر Java Virtual Machine على الساحة. ليس سراً أن الجهاز الظاهري يقرأ الأوامر المقدمة في ملفات الفصل في شكل رمز بايت ويترجمها كتعليمات إلى المعالج. أقترح أن أفهم قليلاً عن مخطط دخول الرمز الثانوي إلى الجهاز الظاهري.

محمل الطبقة

يتم استخدامه لتزويد JVM بالكود الثانوي المترجم، والذي يتم تخزينه عادةً في ملفات ذات الامتداد .class، ولكن يمكن أيضًا الحصول عليه من مصادر أخرى، على سبيل المثال، تنزيله عبر الشبكة أو إنشاؤه بواسطة التطبيق نفسه. كيف يتم تحميل الفئات في JVM - 1وفقًا لمواصفات Java SE، من أجل تشغيل التعليمات البرمجية في JVM، تحتاج إلى إكمال ثلاث خطوات:
  • تحميل الرمز الثانوي من الموارد وإنشاء مثيل للفئةClass

    يتضمن ذلك البحث عن الفئة المطلوبة من بين تلك التي تم تحميلها مسبقًا، والحصول على الرمز الثانوي للتحميل والتحقق من صحته، وإنشاء مثيل للفئة Class(للعمل معها في وقت التشغيل)، وتحميل الفئات الأصلية. إذا لم يتم تحميل الفئات والواجهات الأصلية، فسيتم اعتبار الفئة المعنية غير محملة.

  • ملزمة (أو ربط)

    ووفقا للمواصفات، تنقسم هذه المرحلة إلى ثلاث مراحل أخرى:

    • التحقق ، يتم التحقق من صحة الرمز الثانوي المستلم.
    • التحضير ، وتخصيص ذاكرة الوصول العشوائي (RAM) للحقول الثابتة وتهيئتها بالقيم الافتراضية (في هذه الحالة، التهيئة الصريحة، إن وجدت، تحدث بالفعل في مرحلة التهيئة).
    • الدقة وحل الروابط الرمزية من الأنواع والمجالات والأساليب.
  • تهيئة الكائن المستلم

    هنا، على عكس الفقرات السابقة، يبدو أن كل شيء واضح ما يجب أن يحدث. سيكون من المثير للاهتمام بالطبع أن نفهم بالضبط كيف يحدث هذا.

ويتم تنفيذ جميع هذه الخطوات بالتسلسل مع المتطلبات التالية:
  • يجب أن يتم تحميل الفصل بالكامل قبل ربطه.
  • يجب أن يتم اختبار الفصل وإعداده بالكامل قبل تهيئته.
  • تحدث أخطاء في تحليل الارتباط أثناء تنفيذ البرنامج، حتى لو تم اكتشافها في مرحلة الارتباط.
كما تعلم، تنفذ Java التحميل البطيء (أو البطيء) للفئات. هذا يعني أنه لن يتم تحميل فئات الحقول المرجعية للفئة المحملة حتى يواجه التطبيق مرجعًا صريحًا لها. بمعنى آخر، يعد حل الارتباطات الرمزية أمرًا اختياريًا ولا يحدث افتراضيًا. ومع ذلك، يمكن لتطبيق JVM أيضًا استخدام التحميل النشط للفئة، أي. يجب أن تؤخذ جميع الروابط الرمزية بعين الاعتبار على الفور. ولهذا السبب ينطبق الشرط الأخير. ومن الجدير بالذكر أيضًا أن حل الروابط الرمزية غير مرتبط بأي مرحلة من مراحل تحميل الفصل. بشكل عام، كل مرحلة من هذه المراحل تستحق الدراسة الجيدة؛ فلنحاول معرفة المرحلة الأولى، وهي تحميل الرمز الثانوي.

أنواع محمل جافا

هناك ثلاثة أدوات تحميل قياسية في Java، كل منها يقوم بتحميل فئة من موقع محدد:
  1. يعد Bootstrap مُحملًا أساسيًا، يُسمى أيضًا Primordial ClassLoader.

    يقوم بتحميل فئات JDK القياسية من أرشيف rt.jar

  2. ملحق ClassLoader – محمل الامتداد.

    يقوم بتحميل فئات الامتدادات الموجودة في الدليل jre/lib/ext افتراضيًا، ولكن يمكن تعيينها بواسطة خاصية النظام java.ext.dirs

  3. System ClassLoader – محمل النظام.

    يقوم بتحميل فئات التطبيق المحددة في متغير البيئة CLASSPATH

تستخدم Java تسلسلاً هرميًا لمحملات الفئة، حيث يكون الجذر بالطبع هو الأساس. بعد ذلك يأتي محمل الامتداد، ثم محمل النظام. وبطبيعة الحال، يقوم كل محمل بتخزين مؤشر إلى الأصل حتى يتمكن من تفويض التحميل إليه في حالة عدم قدرته على القيام بذلك.

فئة مجردة ClassLoader

كل محمل، باستثناء الأساسي، هو سليل الفئة المجردة java.lang.ClassLoader. على سبيل المثال، تنفيذ محمل الامتداد هو الفئة sun.misc.Launcher$ExtClassLoader، ومحمل النظام هو sun.misc.Launcher$AppClassLoader. المحمل الأساسي أصلي وتم تضمين تنفيذه في JVM. يمكن لأي فئة ممتدة java.lang.ClassLoaderأن توفر طريقتها الخاصة لتحميل الفئات باستخدام لعبة البلاك جاك وتلك نفسها. للقيام بذلك، من الضروري إعادة تعريف الأساليب المقابلة، والتي في الوقت الحالي لا أستطيع النظر فيها إلا بشكل سطحي، لأنه لم أفهم هذه المسألة بالتفصيل. ها هم:
package java.lang;
public abstract class ClassLoader {
    public Class<?> loadClass(String name);
    protected Class<?> loadClass(String name, boolean resolve);
    protected final Class<?> findLoadedClass(String name);
    public final ClassLoader getParent();
    protected Class<?> findClass(String name);
    protected final void resolveClass(Class<?> c);
}
loadClass(String name)إحدى الطرق العامة القليلة، وهي نقطة الدخول لتحميل الفئات. يتلخص تنفيذه في استدعاء طريقة محمية أخرى loadClass(String name, boolean resolve)، والتي يجب تجاوزها. إذا نظرت إلى Javadoc لهذه الطريقة المحمية، فيمكنك فهم شيء مثل ما يلي: يتم توفير معلمتين كمدخلات. الأول هو الاسم الثنائي للفئة (أو اسم الفئة المؤهل بالكامل) الذي يجب تحميله. يتم تحديد اسم الفئة مع قائمة بجميع الحزم. المعلمة الثانية هي علامة تحدد ما إذا كان تحليل الارتباط الرمزي مطلوبًا أم لا. بشكل افتراضي، يكون false ، مما يعني أنه يتم استخدام تحميل الفئة البطيئة. علاوة على ذلك، وفقًا للوثائق، في التنفيذ الافتراضي للطريقة، يتم إجراء استدعاء findLoadedClass(String name)، والذي يتحقق مما إذا كان قد تم بالفعل تحميل الفئة مسبقًا، وإذا كان الأمر كذلك، يُرجع مرجعًا إلى هذه الفئة. وإلا فسيتم استدعاء طريقة تحميل الفئة الخاصة بالمحمل الأصلي. إذا لم يتمكن أي من القائمين بالتحميل من العثور على فئة محملة، فسيحاول كل منهم، بترتيب عكسي، العثور على تلك الفئة وتحميلها، متجاوزًا ملف findClass(String name). سيتم مناقشة هذا بمزيد من التفصيل في الفصل "مخطط تحميل الفصل". وأخيرًا وليس آخرًا، بعد تحميل الفصل، اعتمادًا على علامة الحل ، سيتم تحديد ما إذا كان سيتم تحميل الفئات عبر روابط رمزية. من الأمثلة الواضحة على ذلك أنه يمكن استدعاء مرحلة الحل أثناء مرحلة تحميل الفصل. وفقًا لذلك، من خلال توسيع الفئة ClassLoaderوتجاوز أساليبها، يمكن للمحمل المخصص تنفيذ منطقه الخاص لتوصيل كود البايت إلى الجهاز الظاهري. تدعم Java أيضًا مفهوم مُحمل الفئة "الحالي". المُحمل الحالي هو الذي قام بتحميل الفئة المُنفذة حاليًا. تعرف كل فئة المُحمل الذي تم تحميلها به، ويمكنك الحصول على هذه المعلومات عن طريق الاتصال بـ String.class.getClassLoader(). بالنسبة لجميع فئات التطبيقات، عادةً ما يكون المحمل "الحالي" هو محمل النظام.

ثلاثة مبادئ لتحميل الفئة

  • وفد

    يتم تمرير طلب تحميل الفئة إلى المُحمل الأصلي، ولا تتم محاولة تحميل الفئة نفسها إلا إذا كان المُحمل الأصلي غير قادر على العثور على الفئة وتحميلها. يتيح لك هذا الأسلوب تحميل الفئات باستخدام المُحمل الأقرب قدر الإمكان إلى المُحمل الأساسي. وهذا يحقق أقصى قدر من الرؤية للفئة. يحتفظ كل مُحمل بسجل للفئات التي تم تحميلها بواسطته، ويضعها في ذاكرة التخزين المؤقت الخاصة به. مجموعة هذه الفئات تسمى النطاق.

  • الرؤية

    يرى المُحمل فقط الفئات "الخاصة به" وفئات "الأصل" وليس لديه أي فكرة عن الفئات التي تم تحميلها بواسطة "الطفل".

  • التفرد

    يمكن تحميل الفصل مرة واحدة فقط. تتأكد آلية التفويض من أن أداة التحميل التي تبدأ تحميل الفئة لا تفرط في تحميل فئة تم تحميلها مسبقًا في JVM.

وبالتالي، عند كتابة أداة تحميل التشغيل الخاصة به، يجب على المطور أن يسترشد بهذه المبادئ الثلاثة.

مخطط تحميل الطبقة

عند حدوث استدعاء لتحميل فئة ما، يتم البحث عن هذه الفئة في ذاكرة التخزين المؤقت للفئات المحملة بالفعل للمحمل الحالي. إذا لم يتم تحميل الفئة المطلوبة من قبل، فإن مبدأ التفويض ينقل التحكم إلى المُحمل الأصلي، والذي يقع في مستوى أعلى في التسلسل الهرمي. يحاول المُحمل الأصلي أيضًا العثور على الفئة المطلوبة في ذاكرة التخزين المؤقت الخاصة به. إذا تم تحميل الفئة بالفعل وكان المُحمل يعرف موقعها، فسيتم Classإرجاع كائن من تلك الفئة. إذا لم يكن الأمر كذلك، فسيستمر البحث حتى يصل إلى أداة تحميل التشغيل الأساسية. إذا لم يكن لدى المُحمل الأساسي معلومات حول الفئة المطلوبة (أي أنه لم يتم تحميلها بعد)، فسيتم البحث عن الرمز الثانوي لهذه الفئة في موقع الفئات التي يعرفها المُحمل، وإذا تعذر العثور على الفئة بعد تحميله، سيعود التحكم مرة أخرى إلى المُحمل الفرعي، والذي سيحاول التحميل من مصادر معروفة له. كما هو مذكور أعلاه، موقع الفئات للمحمل الأساسي هو مكتبة rt.jar، بالنسبة لمحمل الامتداد - الدليل بامتدادات jre/lib/ext، بالنسبة للنظام الأول - CLASSPATH، بالنسبة للمستخدم يمكن أن يكون شيئًا مختلفًا . وبالتالي، فإن تقدم فئات التحميل يذهب في الاتجاه المعاكس - من محمل الجذر إلى التيار. عند العثور على الرمز الثانوي للفئة، يتم تحميل الفئة في JVM ويتم الحصول على مثيل من النوع Class. كما ترون بسهولة، فإن مخطط التحميل الموصوف يشبه تنفيذ الطريقة أعلاه loadClass(String name). أدناه يمكنك رؤية هذا المخطط في الرسم التخطيطي.
كيف يتم تحميل الفئات في JVM - 2

كاستنتاج

في الخطوات الأولى لتعلم اللغة، ليست هناك حاجة خاصة لفهم كيفية تحميل الفئات في Java، ولكن معرفة هذه المبادئ الأساسية ستساعدك على تجنب اليأس عند مواجهة أخطاء مثل ClassNotFoundExceptionأو NoClassDefFoundError. حسنا، أو على الأقل فهم ما هو جذر المشكلة. وبالتالي، يحدث استثناء ClassNotFoundExceptionعندما يتم تحميل فئة ديناميكيًا أثناء تنفيذ البرنامج، عندما يتعذر على أدوات التحميل العثور على الفئة المطلوبة سواء في ذاكرة التخزين المؤقت أو على طول مسار الفئة. لكن الخطأ NoClassDefFoundErrorأكثر خطورة ويحدث عندما تكون الفئة المطلوبة متاحة أثناء التجميع، ولكنها لم تكن مرئية أثناء تنفيذ البرنامج. يمكن أن يحدث هذا إذا نسي البرنامج تضمين المكتبة التي يستخدمها. حسنًا، إن حقيقة فهم مبادئ هيكل الأداة التي تستخدمها في عملك (وليس بالضرورة الانغماس الواضح والمفصل في أعماقها) تضيف بعض الوضوح إلى فهم العمليات التي تحدث داخل هذه الآلية، والتي، في بدوره، يؤدي إلى استخدام واثق لهذه الأداة.

مصادر

كيف يعمل ClassLoader في Java بشكل عام، يعد مصدرًا مفيدًا جدًا مع عرض تقديمي للمعلومات يسهل الوصول إليه. تحميل الفئات، ClassLoader مقالة طويلة جدًا، ولكن مع التركيز على كيفية إنشاء أداة التحميل الخاصة بك باستخدام نفس تلك الفئات. ClassLoader: التحميل الديناميكي للفئات لسوء الحظ، هذا المورد غير متوفر الآن، ولكن هناك وجدت المخطط الأكثر قابلية للفهم مع نظام تحميل الفئات، لذلك لا يسعني إلا إضافته. مواصفات Java SE: الفصل الخامس. التحميل والربط والتهيئة
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION