محتوا:
- معرفی
- کامپایل به بایت کد
- نمونه ای از کامپایل و اجرای برنامه
- اجرای برنامه در ماشین مجازی
- گردآوری به موقع (JIT).
- نتیجه
1. معرفی
سلام به همه! امروز میخواهم دانشی را در مورد آنچه در زیر سرپوش JVM (ماشین مجازی جاوا) بعد از اجرای یک برنامه نوشتاری جاوا اتفاق میافتد، به اشتراک بگذارم. امروزه، محیطهای توسعه مرسوم وجود دارد که به شما کمک میکند از فکر کردن به قسمتهای داخلی JVM، کامپایل و اجرای کد جاوا اجتناب کنید، که میتواند باعث شود توسعهدهندگان جدید این جنبههای مهم را از دست بدهند. در عین حال، اغلب در طول مصاحبه سوالاتی در مورد این موضوع پرسیده می شود، به همین دلیل تصمیم گرفتم مقاله بنویسم.
2. کامپایل به بایت کد
بیایید با تئوری شروع کنیم. وقتی هر اپلیکیشنی را می نویسیم، یک فایل با پسوند ایجاد می کنیم
.java
و کدهایی را در آن به زبان برنامه نویسی جاوا قرار می دهیم. چنین فایلی حاوی کد قابل خواندن توسط انسان،
فایل کد منبع نامیده می شود . هنگامی که فایل کد منبع آماده شد، باید آن را اجرا کنید! اما در مرحله حاوی اطلاعات قابل درک فقط برای انسان است. جاوا یک زبان برنامه نویسی چند پلتفرمی است. این بدان معناست که برنامه های نوشته شده به زبان جاوا را می توان بر روی هر پلتفرمی که یک سیستم زمان اجرا اختصاصی جاوا نصب شده است اجرا کرد. این سیستم ماشین مجازی جاوا (JVM) نام دارد. به منظور ترجمه یک برنامه از کد منبع به کدی که JVM بتواند آن را درک کند، باید آن را کامپایل کنید. کدی که JVM درک می کند بایت کد نامیده می شود و حاوی مجموعه ای از دستورالعمل ها است که ماشین مجازی متعاقباً اجرا خواهد کرد.
javac
برای کامپایل کد منبع به بایت کد، یک کامپایلر در JDK (کیت توسعه جاوا) موجود است . به عنوان ورودی، کامپایلر یک فایل با پسوند
.java
، حاوی کد منبع برنامه را می پذیرد و به عنوان خروجی، فایلی با پسوند تولید می کند که
.class
حاوی بایت کد لازم برای اجرای برنامه توسط ماشین مجازی است. هنگامی که یک برنامه در بایت کد کامپایل شد، می توان آن را با استفاده از ماشین مجازی اجرا کرد.
3. نمونه ای از کامپایل و اجرای برنامه
فرض کنید یک برنامه ساده در یک فایل داریم
Calculator.java
که 2 آرگومان خط فرمان عددی را می گیرد و نتیجه جمع آنها را چاپ می کند:
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
توجه داشته باشید که بعد از نام فایل، 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
مسیری است که ماشین مجازی به دنبال بسته ها و کلاس های کامپایل شده می گردد. یعنی به این ترتیب به ماشین مجازی می گوییم که کدام دایرکتوری ها در سیستم فایل ریشه سلسله مراتب بسته جاوا هستند.
Classpath
را می توان در هنگام شروع برنامه با استفاده از پرچم مشخص کرد
-classpath
. ما برنامه را با استفاده از دستور اجرا می کنیم:
java -classpath ./bin ru.javarush.Calculator 1 2
در این مثال، ما نام کامل کلاس، از جمله نام بسته ای که در آن قرار دارد را میخواهیم. درخت فایل نهایی به شکل زیر است:
├── src
│ └── ru
│ └── javarush
│ └── Calculator.java
└── bin
└── ru
└── javarush
└── Calculator.class
4. اجرای برنامه توسط ماشین مجازی
بنابراین، برنامه مکتوب را راه اندازی کردیم. اما وقتی یک برنامه کامپایل شده توسط یک ماشین مجازی راه اندازی می شود چه اتفاقی می افتد؟ ابتدا بیایید بفهمیم که مفاهیم کامپایل و تفسیر کد به چه معناست.
کامپایل ترجمه برنامه ای است که به زبان مبدأ سطح بالا نوشته شده است به یک برنامه معادل به زبان سطح پایین شبیه به کد ماشین.
تفسیر یک عملگر به بیانیه (دستور به خط، خط به خط) تجزیه و تحلیل، پردازش و اجرای فوری برنامه یا درخواست منبع است (برخلاف کامپایل که در آن برنامه بدون اجرای آن ترجمه می شود). زبان جاوا هم یک کامپایلر (
javac
) و هم یک مفسر دارد که یک ماشین مجازی است که بایت کد را خط به خط به کد ماشین تبدیل می کند و بلافاصله آن را اجرا می کند. بنابراین، وقتی یک برنامه کامپایل شده را اجرا می کنیم، ماشین مجازی شروع به تفسیر آن می کند، یعنی تبدیل خط به خط بایت کد به کد ماشین و همچنین اجرای آن. متأسفانه، تفسیر بایت کد خالص یک فرآیند نسبتا طولانی است و جاوا را در مقایسه با رقبای خود کند می کند. برای جلوگیری از این امر، مکانیزمی برای سرعت بخشیدن به تفسیر بایت کد توسط ماشین مجازی معرفی شد. به این مکانیسم، کامپایل فقط در زمان (JITC) می گویند.
5. تالیف Just-in-time (JIT).
به عبارت ساده، مکانیسم کامپایل Just-In-Time به این صورت است: اگر قسمت هایی از کد در برنامه وجود دارد که بارها اجرا می شوند، می توان آنها را یک بار در کد ماشین کامپایل کرد تا در آینده سرعت اجرای آنها افزایش یابد. پس از کامپایل چنین بخشی از برنامه در کد ماشین، با هر فراخوانی بعدی به این قسمت از برنامه، ماشین مجازی به جای تفسیر آن، بلافاصله کد ماشین کامپایل شده را اجرا می کند، که طبیعتا اجرای برنامه را سرعت می بخشد. افزایش سرعت برنامه با افزایش مصرف حافظه (ما باید کد ماشین کامپایل شده را در جایی ذخیره کنیم!) و با افزایش زمان صرف شده برای کامپایل در طول اجرای برنامه به دست می آید. کامپایل JIT یک مکانیسم نسبتاً پیچیده است، بنابراین بیایید به بالا برویم. 4 سطح از کامپایل JIT بایت کد در کد ماشین وجود دارد. هر چه سطح کامپایل بالاتر باشد، پیچیدگی آن بیشتر است، اما در عین حال اجرای چنین بخشی سریعتر از قسمتی با سطح پایین تر خواهد بود. JIT - کامپایلر بر اساس تعداد دفعات اجرای آن قطعه تصمیم می گیرد که چه سطح کامپایل را برای هر قطعه برنامه تنظیم کند. در زیر هود، JVM از 2 کامپایلر JIT - C1 و C2 استفاده می کند. کامپایلر C1 را کامپایلر کلاینت نیز می نامند و فقط تا سطح 3 قادر به کامپایل کد می باشد. کامپایلر C2 مسئول چهارمین، پیچیده ترین و سریع ترین سطح کامپایل است.
از موارد فوق می توان نتیجه گرفت که برای برنامه های کلاینت ساده، استفاده از کامپایلر C1 سودآورتر است، زیرا در این مورد برای ما مهم است که برنامه با چه سرعتی شروع می شود. برنامههای کاربردی در سمت سرور و با عمر طولانیتر ممکن است شروع به کار کنند، اما در آینده باید کار کنند و عملکرد خود را سریع انجام دهند - در اینجا کامپایلر C2 برای ما مناسب است.
هنگام اجرای یک برنامه جاوا بر روی نسخه x32 JVM، میتوانیم به صورت دستی با استفاده از
-client
و flags مشخص کنیم که از کدام حالت استفاده کنیم
-server
. هنگامی که این پرچم مشخص می شود،
-client
JVM بهینه سازی های پیچیده بایت کد را انجام نمی دهد، که باعث افزایش سرعت راه اندازی برنامه و کاهش میزان حافظه مصرف شده می شود. هنگام تعیین پرچم،
-server
برنامه به دلیل بهینهسازیهای پیچیده کد بایت شروع به کار بیشتر طول میکشد و از حافظه بیشتری برای ذخیره کد ماشین استفاده میکند، اما برنامه در آینده سریعتر اجرا میشود. در نسخه x64 JVM، پرچم
-client
نادیده گرفته می شود و پیکربندی سرور برنامه به طور پیش فرض استفاده می شود.
6. نتیجه گیری
این به پایان می رسد مروری کوتاه من در مورد نحوه کار کامپایل و اجرای یک برنامه جاوا. نکات اصلی:
-
کامپایلر javac کد منبع برنامه را به بایت کد تبدیل می کند که می تواند بر روی هر پلتفرمی که ماشین مجازی جاوا روی آن نصب شده است اجرا شود.
-
پس از کامپایل، JVM بایت کد حاصل را تفسیر می کند.
-
برای افزایش سرعت برنامههای جاوا، JVM از مکانیزم کامپایلسازی Just-In-Time استفاده میکند که اغلب بخشهای اجرا شده یک برنامه را به کد ماشین تبدیل کرده و در حافظه ذخیره میکند.
امیدوارم این مقاله به شما کمک کرده باشد تا درک عمیق تری از نحوه عملکرد زبان برنامه نویسی مورد علاقه ما به دست آورید. با تشکر برای خواندن، انتقاد پذیرفته می شود!
GO TO FULL VERSION