JavaRush /בלוג Java /Random-HE /קומפילציה והפעלה של יישומי Java מתחת למכסה המנוע
Павел Голов
רָמָה
Москва

קומפילציה והפעלה של יישומי Java מתחת למכסה המנוע

פורסם בקבוצה

תוֹכֶן:

  1. מבוא
  2. קומפילציה לקוד בייט
  3. דוגמה להידור וביצוע תוכנית
  4. הפעלת תוכנית במכונה וירטואלית
  5. קומפילציה של Just-in-Time (JIT).
  6. סיכום
קומפילציה והפעלה של יישומי Java מתחת למכסה המנוע - 1

1. הקדמה

שלום לכולם! היום ברצוני לחלוק ידע על מה שקורה מתחת למכסה המנוע של ה-JVM (Java Virtual Machine) לאחר שהרצנו יישום שנכתב ב-Java. כיום, ישנן סביבות פיתוח אופנתיות שעוזרות לכם להימנע מלחשוב על הפנימיות של ה-JVM, קומפילציה וביצוע של קוד Java, מה שעלול לגרום למפתחים חדשים לפספס את ההיבטים החשובים הללו. יחד עם זאת, שאלות בנושא זה נשאלות פעמים רבות במהלך ראיונות, ולכן החלטתי לכתוב מאמר.

2. קומפילציה לקוד בייט

קומפילציה והפעלה של יישומי Java מתחת למכסה המנוע - 2
נתחיל מהתיאוריה. כאשר אנו כותבים אפליקציה כלשהי, אנו יוצרים קובץ עם סיומת .javaוממקמים בו קוד בשפת התכנות Java. קובץ כזה המכיל קוד קריא אנושי נקרא קובץ קוד מקור . לאחר שקובץ קוד המקור מוכן, עליך לבצע אותו! אבל בשלב זה מכיל מידע המובן רק לבני אדם. Java היא שפת תכנות מרובת פלטפורמות. המשמעות היא שתוכנות שנכתבו ב-Java ניתנות להפעלה בכל פלטפורמה שמותקנת בה מערכת זמן ריצה ייעודית של Java. מערכת זו נקראת Java Virtual Machine (JVM). על מנת לתרגם תוכנית מקוד מקור לקוד שה-JVM יכול להבין, עליך להדר אותו. הקוד המובן על ידי ה-JVM נקרא bytecode ומכיל קבוצה של הוראות שהמכונה הוירטואלית תבצע לאחר מכן. כדי להדר קוד מקור לתוך bytecode, יש מהדר javacכלול ב-JDK (ערכת פיתוח ג'אווה). כקלט, המהדר מקבל קובץ עם הסיומת .java, המכיל את קוד המקור של התוכנית, וכפלט, הוא מייצר קובץ עם הסיומת .class, המכיל את קוד הבתים הדרוש להפעלת התוכנית על ידי המחשב הווירטואלי. לאחר הידור של תוכנית לתוך bytecode, ניתן להפעיל אותה באמצעות מכונה וירטואלית.

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);
    }
}
על מנת להדר תוכנית זו לקוד byte, נשתמש במהדר javacבשורת הפקודה:
javac Calculator.java
לאחר ההידור, אנו מקבלים קובץ עם bytecode כפלט Calculator.class, אותו נוכל לבצע באמצעות מכונת ה-java המותקנת על המחשב שלנו באמצעות הפקודה java בשורת הפקודה:
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מראש. טכניקה זו משמשת כדי למנוע בלבול בין קובצי קוד מקור לקבצי bytecode. לפני הפעלת תוכנית ההידור, כדאי להסביר את הרעיון 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) וגם מתורגמן, שהוא מכונה וירטואלית שממירה קוד בתים לקוד מכונה שורה אחר שורה ומפעילה אותו מיד. לפיכך, כאשר אנו מריצים תוכנית מהודרת, המכונה הוירטואלית מתחילה לפרש אותה, כלומר המרה שורה אחר שורה של קוד בתים לקוד מכונה, כמו גם ביצועו. למרבה הצער, פרשנות קוד בתים טהורה היא תהליך ארוך למדי והופך את ג'אווה לאיטי בהשוואה למתחרותיה. כדי להימנע מכך, הוכנס מנגנון לזרז את הפרשנות של bytecode על ידי המכונה הוירטואלית. מנגנון זה נקרא הידור Just-in-time (JITC).

5. קומפילציה של Just-in-Time (JIT).

במילים פשוטות, המנגנון של הידור Just-In-Time הוא כזה: אם יש חלקים מהקוד בתוכנית שמופעלים פעמים רבות, אז ניתן להידור אותם פעם אחת לתוך קוד מכונה כדי לזרז את הביצוע שלהם בעתיד. לאחר קומפילציה של חלק כזה של התוכנית לקוד מכונה, עם כל קריאה שלאחר מכן לחלק זה של התוכנית, המכונה הוירטואלית תבצע מיד את קוד המכונה המהודר במקום לפרש אותו, מה שמטבע הדברים יאיץ את ביצוע התוכנית. זירוז התוכנית מושג על ידי הגדלת צריכת הזיכרון (אנחנו צריכים לאחסן את קוד המכונה המהודר איפשהו!) ועל ידי הגדלת הזמן המושקע בהידור במהלך הפעלת התוכנית. קומפילציה של JIT היא מנגנון מורכב למדי, אז בואו נעבור למעלה. ישנן 4 רמות של הידור JIT של קוד בתים לקוד מכונה. ככל שרמת הקומפילציה גבוהה יותר כך היא מורכבת יותר, אך יחד עם זאת הביצוע של קטע כזה יהיה מהיר יותר מקטע עם רמה נמוכה יותר. JIT - המהדר מחליט איזו רמת קומפילציה להגדיר עבור כל קטע תוכנית בהתבסס על התדירות של ביצוע הפרגמנט הזה. מתחת למכסה המנוע, ה-JVM משתמש ב-2 מהדרים של JIT - C1 ו-C2. מהדר C1 נקרא גם מהדר לקוח והוא מסוגל להדר קוד רק עד לרמה 3. המהדר 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