JavaRush /จาวาบล็อก /Random-TH /วิธีโหลดคลาสใน JVM
Aleksandr Zimin
ระดับ
Санкт-Петербург

วิธีโหลดคลาสใน JVM

เผยแพร่ในกลุ่ม
หลังจากที่ส่วนที่ยากที่สุดในงานของโปรแกรมเมอร์เสร็จสิ้นและเขียนแอปพลิเคชัน “Hello World 2.0” แล้ว สิ่งที่เหลืออยู่คือการประกอบชุดแจกจ่ายและโอนไปยังลูกค้า หรืออย่างน้อยก็ไปยังบริการทดสอบ ในการกระจายทุกอย่างเป็นไปตามที่ควรจะเป็น และเมื่อเราเปิดตัวโปรแกรม Java Virtual Machine ก็เข้ามามีบทบาท ไม่มีความลับใดที่เครื่องเสมือนจะอ่านคำสั่งที่นำเสนอในไฟล์คลาสในรูปแบบของ bytecode และแปลเป็นคำสั่งของโปรเซสเซอร์ ฉันเสนอให้เข้าใจเล็กน้อยเกี่ยวกับโครงร่างของ bytecode ที่เข้าสู่เครื่องเสมือน

ตัวโหลดคลาส

มันถูกใช้เพื่อจัดหารหัสไบต์ที่คอมไพล์ให้กับ JVM ซึ่งโดยปกติจะถูกเก็บไว้ในไฟล์ที่มีนามสกุล.classแต่ยังสามารถรับได้จากแหล่งอื่น เช่น ดาวน์โหลดผ่านเครือข่ายหรือสร้างโดยแอปพลิเคชันเอง วิธีโหลดคลาสใน JVM - 1ตามข้อกำหนดของ Java SE เพื่อให้ได้โค้ดที่รันใน JVM คุณต้องดำเนินการสามขั้นตอนให้เสร็จสิ้น:
  • กำลังโหลด bytecode จากทรัพยากรและสร้างอินสแตนซ์ของคลาสClass

    ซึ่งรวมถึงการค้นหาคลาสที่ร้องขอจากคลาสที่โหลดก่อนหน้านี้ การได้รับ bytecode สำหรับการโหลดและตรวจสอบความถูกต้อง การสร้างอินสแตนซ์ของคลาสClass(สำหรับการทำงานกับคลาสในขณะรันไทม์) และการโหลดคลาสพาเรนต์ หากไม่ได้โหลดคลาสพาเรนต์และอินเทอร์เฟซ จะถือว่าคลาสดังกล่าวไม่ได้โหลด

  • การผูก (หรือการเชื่อมโยง)

    ตามข้อกำหนดขั้นตอนนี้แบ่งออกเป็นสามขั้นตอนเพิ่มเติม:

    • การตรวจสอบความถูกต้องของรหัสไบต์ที่ได้รับจะถูกตรวจสอบ
    • การเตรียมการ จัดสรร RAM สำหรับฟิลด์คงที่และกำหนดค่าเริ่มต้นด้วยค่าเริ่มต้น (ในกรณีนี้ การกำหนดค่าเริ่มต้นที่ชัดเจน (ถ้ามี) เกิดขึ้นแล้วในขั้นตอนการเริ่มต้น)
    • ความละเอียดความละเอียดของลิงก์สัญลักษณ์ประเภท ฟิลด์ และวิธีการ
  • การเริ่มต้นวัตถุที่ได้รับ

    ที่นี่ต่างจากย่อหน้าก่อนๆ ทุกอย่างดูชัดเจนว่าจะเกิดอะไรขึ้น แน่นอนว่าเป็นเรื่องน่าสนใจที่จะเข้าใจว่าสิ่งนี้เกิดขึ้นได้อย่างไร

ขั้นตอนทั้งหมดเหล่านี้ดำเนินการตามลำดับตามข้อกำหนดต่อไปนี้:
  • คลาสจะต้องโหลดเต็มก่อนที่จะลิงก์
  • คลาสจะต้องได้รับการทดสอบอย่างเต็มที่และเตรียมพร้อมก่อนที่จะเริ่มต้นได้
  • ข้อผิดพลาดในการแก้ไขลิงก์เกิดขึ้นระหว่างการทำงานของโปรแกรม แม้ว่าจะถูกตรวจพบในขั้นตอนการลิงก์ก็ตาม
ดังที่คุณทราบ Java ใช้การโหลดคลาสแบบขี้เกียจ (หรือแบบขี้เกียจ) ซึ่งหมายความว่าการโหลดคลาสของฟิลด์อ้างอิงของคลาสที่โหลดจะไม่ถูกดำเนินการจนกว่าแอปพลิเคชันจะพบการอ้างอิงที่ชัดเจน กล่าวอีกนัยหนึ่ง การแก้ไขลิงก์สัญลักษณ์เป็นทางเลือกและไม่เกิดขึ้นตามค่าดีฟอลต์ อย่างไรก็ตาม การใช้งาน JVM ยังสามารถใช้การโหลดคลาสที่มีพลังได้ เช่น ลิงค์สัญลักษณ์ทั้งหมดจะต้องนำมาพิจารณาทันที สำหรับจุดนี้เองที่ข้อกำหนดสุดท้ายมีผลบังคับใช้ เป็นที่น่าสังเกตว่าความละเอียดของลิงก์สัญลักษณ์ไม่ได้เชื่อมโยงกับขั้นตอนใด ๆ ของการโหลดคลาส โดยทั่วไป แต่ละขั้นตอนเหล่านี้มีไว้สำหรับการศึกษาที่ดี เรามาลองหาขั้นตอนแรกกัน นั่นคือการโหลด bytecode

ประเภทของ Java Loader

มีตัวโหลดมาตรฐานสามตัวใน 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.ClassLoaderabstract ตัวอย่างเช่น การใช้งานตัวโหลดส่วนขยายคือคลาส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 ก่อนหน้านี้

ดังนั้นเมื่อเขียน bootloader นักพัฒนาควรได้รับคำแนะนำจากหลักการทั้งสามนี้

รูปแบบการโหลดชั้นเรียน

เมื่อมีการเรียกให้โหลดคลาสเกิดขึ้น คลาสนี้จะถูกค้นหาในแคชของคลาสที่โหลดไว้แล้วของตัวโหลดปัจจุบัน หากไม่เคยโหลดคลาสที่ต้องการมาก่อน หลักการของการมอบหมายจะโอนการควบคุมไปยังตัวโหลดพาเรนต์ ซึ่งอยู่ในลำดับชั้นที่สูงกว่าหนึ่งระดับ ตัวโหลดพาเรนต์ยังพยายามค้นหาคลาสที่ต้องการในแคช หากคลาสถูกโหลดไปแล้วและตัวโหลดทราบตำแหน่งของคลาสนั้น อ็อบเจ็กต์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: บทที่ 5 การโหลด การเชื่อมโยง และการเริ่มต้น
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION