JavaRush /בלוג Java /Random-HE /כיצד נטענים שיעורים ל-JVM
Aleksandr Zimin
רָמָה
Санкт-Петербург

כיצד נטענים שיעורים ל-JVM

פורסם בקבוצה
לאחר שהסתיים החלק הקשה ביותר בעבודתו של מתכנת ונכתבה אפליקציית "Hello World 2.0", כל שנותר הוא להרכיב את ערכת ההפצה ולהעבירה ללקוח, או לפחות לשירות הבדיקות. בהפצה, הכל כמו שצריך, וכשאנחנו משיקים את התוכנית שלנו, ה-Java Virtual Machine עולה לזירה. זה לא סוד שהמכונה הוירטואלית קוראת פקודות המוצגות בקבצי מחלקה בצורה של bytecode ומתרגמת אותן כהוראות למעבד. אני מציע להבין קצת על סכימת ה-bytecode להיכנס למכונה הוירטואלית.

מטעין כיתתי

הוא משמש לאספקת קוד בתים מהידור ל-JVM, אשר מאוחסן בדרך כלל בקבצים עם הסיומת .class, אך ניתן להשיגו גם ממקורות אחרים, למשל, הורדה דרך הרשת או שנוצרה על ידי האפליקציה עצמה. כיצד נטענים שיעורים ב-JVM - 1על פי מפרט Java SE, על מנת להפעיל קוד ב-JVM, עליך לבצע שלושה שלבים:
  • טעינת bytecode ממשאבים ויצירת מופע של המחלקהClass

    זה כולל חיפוש אחר המחלקה המבוקשת בין אלה שנטענו קודם לכן, השגת קוד בתים לטעינה ובדיקת נכונותו, יצירת מופע של המחלקה Class(לעבודה איתה בזמן ריצה) וטעינת מחלקות אב. אם מחלקות וממשקי אב לא נטענו, אזי המחלקה המדוברת נחשבת לא נטענת.

  • מחייב (או קישור)

    על פי המפרט, שלב זה מחולק לשלושה שלבים נוספים:

    • אימות , נבדקת נכונות ה-bytecode שהתקבל.
    • הכנה , הקצאת זיכרון RAM לשדות סטטיים ואתחולם עם ערכי ברירת מחדל (במקרה זה, אתחול מפורש, אם קיים, מתרחש כבר בשלב האתחול).
    • רזולוציה , רזולוציה של קישורים סמליים של סוגים, שדות ושיטות.
  • אתחול האובייקט שהתקבל

    כאן, בניגוד לפסקאות הקודמות, נראה שהכל ברור מה צריך לקרות. זה יהיה, כמובן, מעניין להבין איך זה קורה בדיוק.

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

סוגי מעמיסי Java

ישנם שלושה מעמיסים סטנדרטיים ב-Java, שכל אחד מהם טוען מחלקה ממיקום ספציפי:
  1. Bootstrap הוא מטעין בסיסי, הנקרא גם Primordial ClassLoader.

    טוען מחלקות JDK סטנדרטיות מארכיון rt.jar

  2. Extension 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). זה יידון ביתר פירוט בפרק "תכנית טעינת כיתות". ולבסוף, אחרון חביב, לאחר טעינת המחלקה, בהתאם לדגל ה- resolve , יוחלט האם לטעון מחלקות באמצעות קישורים סמליים. דוגמה ברורה היא שניתן לקרוא לשלב הרזולוציה בשלב טעינת המחלקה. בהתאם לכך, על ידי הרחבת המחלקה ClassLoaderועקוף השיטות שלה, הטוען המותאם אישית יכול ליישם את ההיגיון שלו להעברת קוד בתים למחשב הוירטואלי. Java גם תומכת בקונספט של מטעין מחלקות "נוכחי". הטוען הנוכחי הוא זה שטען את המחלקה המבצעת כעת. כל מחלקה יודעת באיזה מטעין נטען, ותוכלו לקבל מידע זה על ידי קריאה שלו String.class.getClassLoader(). עבור כל מחלקות היישומים, המעמיס ה"נוכחי" הוא בדרך כלל המערכת.

שלושה עקרונות של טעינת כיתות

  • מִשׁלַחַת

    הבקשה לטעון את המחלקה מועברת למטעין האב, וניסיון לטעון את המחלקה עצמה מתבצע רק אם טוען האב לא הצליח למצוא ולטעון את המחלקה. גישה זו מאפשרת לך לטעון מחלקות עם המעמיס הקרוב ככל האפשר לבסיס. זה משיג נראות מעמדית מקסימלית. כל מטעין שומר תיעוד של השיעורים שנטענו על ידו, וממקם אותם במטמון שלו. קבוצת המחלקות הללו נקראת היקף.

  • רְאוּת

    המעמיס רואה רק את הכיתות "שלו" ואת הכיתות של "ההורה" ואין לו מושג לגבי השיעורים שהועלו על ידי "הילד".

  • ייחודיות

    ניתן לטעון מחלקה פעם אחת בלבד. מנגנון האצלה מוודא שהמטעין שיוזם טעינת מחלקה לא יעמיס מחלקה שנטענה בעבר ל-JVM.

לפיכך, בעת כתיבת מאתחול האתחול שלו, מפתח צריך להיות מונחה על ידי שלושת העקרונות הללו.

ערכת טעינת כיתות

כאשר מתרחשת קריאה לטעינת מחלקה, מחלקה זו מתבצעת בחיפוש במטמון של מחלקות שנטענו כבר של הטוען הנוכחי. אם המחלקה הרצויה לא נטענה בעבר, עקרון האצלה מעביר את השליטה למטעין האב, שנמצא ברמה אחת גבוה יותר בהיררכיה. גם מטעין האב מנסה למצוא את המחלקה הרצויה במטמון שלו. אם המחלקה כבר נטענה והמטעין יודע את מיקומה, אזי Classיוחזר אובייקט מאותה מחלקה. אם לא, החיפוש יימשך עד שהוא יגיע למטען האתחול הבסיסי. אם למטעין הבסיס אין מידע על המחלקה הנדרשת (כלומר, הוא עדיין לא נטען), ה-bytecode של המחלקה הזו יחפש במיקום המחלקות שהמטעין הנתון יודע עליהן, ואם המחלקה לא יכולה להיטען, הבקרה תחזור למטעין הילד, שינסה לטעון ממקורות המוכרים לו. כפי שצוין לעיל, מיקום המחלקות עבור מטעין הבסיס הוא ספריית rt.jar, עבור טוען ההרחבה - הספרייה עם הרחבות jre/lib/ext, עבור המערכת אחת - CLASSPATH, עבור המשתמש זה יכול להיות משהו שונה . לפיכך, ההתקדמות של מחלקות הטעינה הולכת בכיוון ההפוך - ממטעין השורש אל הנוכחי. כאשר ה-bytecode של המחלקה נמצא, המחלקה נטענת לתוך ה-JVM ומתקבל מופע מהסוג Class. כפי שאתה יכול לראות בקלות, ערכת הטעינה המתוארת דומה ליישום של השיטה לעיל loadClass(String name). להלן ניתן לראות תרשים זה בתרשים.
כיצד נטענים שיעורים ב-JVM - 2

כמסקנה

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

מקורות

איך ClassLoader עובד ב-Java בסך הכל מקור שימושי מאוד עם מצגת נגישה של מידע. טוען שיעורים, ClassLoader מאמר די ארוך, אבל עם דגש על איך לעשות יישום מטעין משלך עם אותם אלה. ClassLoader: טעינה דינמית של מחלקות לצערי, משאב זה אינו זמין כעת, אבל שם מצאתי את הדיאגרמה הכי מובנת עם סכימת טעינת מחלקות, אז אני לא יכול שלא להוסיף אותו. מפרט Java SE: פרק 5. טעינה, קישור, ואתחול
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION