JavaRush /مدونة جافا /Random-AR /تجميع وتنفيذ تطبيقات Java تحت الغطاء
Павел Голов
مستوى
Москва

تجميع وتنفيذ تطبيقات Java تحت الغطاء

نشرت في المجموعة

محتوى:

  1. مقدمة
  2. تجميع إلى bytecode
  3. مثال على تجميع البرنامج وتنفيذه
  4. تنفيذ برنامج على جهاز ظاهري
  5. التجميع في الوقت المناسب (JIT).
  6. خاتمة
تجميع وتنفيذ تطبيقات Java تحت الغطاء - 1

1 المقدمة

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

2. التحويل البرمجي إلى bytecode

تجميع وتنفيذ تطبيقات Java تحت الغطاء - 2
لنبدأ بالنظرية. عندما نكتب أي تطبيق، نقوم بإنشاء ملف بامتداد .javaونضع فيه الكود بلغة البرمجة جافا. يسمى هذا الملف الذي يحتوي على تعليمات برمجية يمكن للإنسان قراءتها بملف التعليمات البرمجية المصدر . بمجرد أن يصبح ملف التعليمات البرمجية المصدر جاهزًا، ستحتاج إلى تنفيذه! لكن في هذه المرحلة تحتوي على معلومات لا يفهمها إلا الإنسان. Java هي لغة برمجة متعددة المنصات. وهذا يعني أنه يمكن تنفيذ البرامج المكتوبة بلغة Java على أي نظام أساسي مثبت عليه نظام تشغيل Java مخصص. يسمى هذا النظام Java Virtual Machine (JVM). من أجل ترجمة برنامج من كود المصدر إلى كود يمكن لـ JVM فهمه، تحتاج إلى تجميعه. يُطلق على الكود الذي يفهمه JVM اسم bytecode ويحتوي على مجموعة من التعليمات التي سينفذها الجهاز الظاهري لاحقًا. لتجميع التعليمات البرمجية المصدر إلى كود بايت، يوجد مترجم مضمن javacفي JDK (Java Development Kit). كمدخل، يقبل المترجم ملفًا بالامتداد .java، يحتوي على الكود المصدري للبرنامج، وكمخرج، ينتج ملفًا بالامتداد .class، يحتوي على الرمز الثانوي اللازم لتنفيذ البرنامج بواسطة الجهاز الظاهري. بمجرد تجميع البرنامج في كود بايت، يمكن تنفيذه باستخدام جهاز افتراضي.

3. مثال على تجميع البرنامج وتنفيذه

لنفترض أن لدينا برنامجًا بسيطًا، موجودًا في ملف Calculator.java، يأخذ وسيطتين رقميتين لسطر الأوامر ويطبع نتيجة إضافتهما:
class Calculator {
    public static void main(String[] args){
        int a = Integer.valueOf(args[0]);
        int b = Integer.valueOf(args[1]);

        System.out.println(a + b);
    }
}
من أجل ترجمة هذا البرنامج إلى رمز بايت، سوف نستخدم المترجم javacفي سطر الأوامر:
javac Calculator.java
بعد التجميع، نتلقى ملفًا به رمز بايت كمخرج Calculator.class، والذي يمكننا تنفيذه باستخدام جهاز جافا المثبت على جهاز الكمبيوتر الخاص بنا باستخدام أمر جافا في سطر الأوامر:
java Calculator 1 2
لاحظ أنه بعد اسم الملف تم تحديد وسيطتين لسطر الأوامر - الرقم 1 و 2. بعد تنفيذ البرنامج، سيتم عرض الرقم 3 في سطر الأوامر، في المثال أعلاه، كان لدينا فئة بسيطة تعيش بمفردها . ولكن ماذا لو كان الفصل في بعض الحزم؟ دعونا نحاكي الموقف التالي: قم بإنشاء الأدلة src/ru/javarushووضع فصلنا هناك. الآن يبدو الأمر هكذا (أضفنا اسم الحزمة في بداية الملف):
package ru.javarush;

class Calculator {
    public static void main(String[] args){
        int a = Integer.valueOf(args[0]);
        int b = Integer.valueOf(args[1]);

        System.out.println(a + b);
    }
}
لنقم بتجميع مثل هذه الفئة باستخدام الأمر التالي:
javac -d bin src/ru/javarush/Calculator.java
في هذا المثال، استخدمنا خيار مترجم إضافي -d binيضع الملفات المترجمة في دليل binببنية مشابهة للدليل src، ولكن يجب إنشاء الدليل binمسبقًا. تُستخدم هذه التقنية لتجنب الخلط بين ملفات التعليمات البرمجية المصدر وملفات التعليمات البرمجية الثانوية. قبل تشغيل البرنامج المترجم، يجدر شرح المفهوم classpath. Classpathهو المسار المتعلق الذي سيبحث فيه الجهاز الظاهري عن الحزم والفئات المترجمة. أي أننا بهذه الطريقة نخبر الجهاز الظاهري عن الأدلة الموجودة في نظام الملفات والتي تمثل جذر التسلسل الهرمي لحزمة Java. Classpathيمكن تحديدها عند بدء تشغيل البرنامج باستخدام العلم -classpath. نطلق البرنامج باستخدام الأمر:
java -classpath ./bin ru.javarush.Calculator 1 2
في هذا المثال، طلبنا الاسم الكامل للفئة، بما في ذلك اسم الحزمة التي توجد بها. تبدو شجرة الملف النهائية كما يلي:
├── src
│     └── ru
│          └── javarush
│                  └── Calculator.java
└── bin
      └── ru
           └── javarush
                   └── Calculator.class

4. تنفيذ البرنامج بواسطة جهاز افتراضي

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

5. التجميع في الوقت المناسب (JIT).

بعبارات بسيطة، آلية التجميع في الوقت المناسب هي كما يلي: إذا كانت هناك أجزاء من التعليمات البرمجية في البرنامج يتم تنفيذها عدة مرات، فيمكن تجميعها مرة واحدة في رمز الآلة لتسريع تنفيذها في المستقبل. بعد تجميع هذا الجزء من البرنامج في رمز الجهاز، مع كل استدعاء لاحق لهذا الجزء من البرنامج، ستقوم الآلة الافتراضية بتنفيذ رمز الجهاز المترجم على الفور بدلاً من تفسيره، مما سيؤدي بشكل طبيعي إلى تسريع تنفيذ البرنامج. يتم تسريع البرنامج عن طريق زيادة استهلاك الذاكرة (نحتاج إلى تخزين كود الجهاز المترجم في مكان ما!) وعن طريق زيادة الوقت المستغرق في الترجمة أثناء تنفيذ البرنامج. تجميع JIT هو آلية معقدة إلى حد ما، لذلك دعونا نذهب إلى الأعلى. هناك 4 مستويات من تجميع JIT للكود الثانوي في كود الجهاز. كلما ارتفع مستوى الترجمة، كلما كان الأمر أكثر تعقيدا، ولكن في نفس الوقت سيكون تنفيذ هذا القسم أسرع من القسم ذو المستوى الأدنى. JIT - يقرر المترجم مستوى الترجمة الذي سيتم تعيينه لكل جزء من البرنامج بناءً على عدد مرات تنفيذ هذا الجزء. تحت الغطاء، يستخدم JVM مترجمين JIT - C1 وC2. يُطلق على برنامج التحويل البرمجي C1 أيضًا اسم برنامج التحويل البرمجي للعميل وهو قادر على تجميع التعليمات البرمجية حتى المستوى الثالث فقط. المترجم C2 هو المسؤول عن مستوى التجميع الرابع والأكثر تعقيدًا والأسرع.
تجميع وتنفيذ تطبيقات Java تحت الغطاء - 3
مما سبق، يمكننا أن نستنتج أنه بالنسبة لتطبيقات العميل البسيطة، من المربح أكثر استخدام مترجم C1، لأنه في هذه الحالة من المهم بالنسبة لنا مدى سرعة بدء التطبيق. يمكن أن تستغرق التطبيقات طويلة العمر من جانب الخادم وقتًا أطول للبدء، ولكن في المستقبل يجب أن تعمل وتؤدي وظيفتها بسرعة - وهنا يكون مترجم C2 مناسبًا لنا. عند تشغيل برنامج Java على الإصدار x32 من JVM، يمكننا يدويًا تحديد الوضع الذي نريد استخدامه باستخدام العلامات -clientو -server. عند تحديد هذه العلامة، -clientلن يقوم JVM بإجراء تحسينات معقدة على الكود الثانوي، مما سيؤدي إلى تسريع وقت بدء تشغيل التطبيق وتقليل مقدار الذاكرة المستهلكة. عند تحديد العلامة، -serverسيستغرق التطبيق وقتًا أطول لبدء التشغيل بسبب تحسينات الكود الثانوي المعقدة وسيستخدم المزيد من الذاكرة لتخزين كود الجهاز، لكن البرنامج سيعمل بشكل أسرع في المستقبل. في الإصدار x64 من JVM، -clientيتم تجاهل العلامة ويتم استخدام تكوين خادم التطبيق بشكل افتراضي.

6. الاستنتاج

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