เมื่อทำการพัฒนา Java ความรู้พื้นฐานที่คุณต้องคุ้นเคยในกลไก Classloader บทความนี้สรุปกลไก Java Classloader สั้น ๆ เนื่องจากการใช้งาน JVM ที่แตกต่างกันนั้นแตกต่างกันเนื้อหาที่อธิบายไว้ในบทความนี้จึง จำกัด เฉพาะ Hotspot JVM
บทความนี้จะเริ่มต้นด้วยสี่ด้านของ classloader รูปแบบการมอบหมายหลักที่จัดทำโดย JDK วิธีการปรับแต่ง classloader และสถานการณ์ที่ทำลายกลไกการมอบหมายผู้ปกครองใน Java
JDK เริ่มต้น classloader
JDK จัดเตรียม classloaders ต่อไปนี้ตามค่าเริ่มต้น
bootstrp loader
bootstrp loader เขียนเป็นภาษา C ++ มันเริ่มต้นหลังจากเริ่มเครื่องเสมือน Java ส่วนใหญ่มีหน้าที่รับผิดชอบในการโหลดพา ธ ที่ระบุโดย %java_home %/jre/lib, -xbootclasspath พารามิเตอร์และคลาสใน %java_home %/jre/คลาส
extclassloader
bootstrp loader โหลด extclassloader และตั้งค่าตัวโหลดหลักของ extclassloader เป็น bootstrp loader.extclassloader เขียนใน Java โดยเฉพาะ Sun.misc.launcher $ extclassloader extclassloader ส่วนใหญ่โหลด %java_home %/jre/lib/ext, ไดเรกทอรีคลาสทั้งหมดภายใต้เส้นทางนี้และไลบรารีคลาสนี้ในเส้นทางที่ระบุโดยตัวแปรระบบ java.ext.dirs
AppClassLoader
หลังจากตัวโหลด bootSTRP โหลด extclassloader แล้ว AppClassLoader จะถูกโหลดและตัวโหลดหลักของ AppClassLoader จะถูกระบุเป็น extClassLoader AppClassLoader ยังเขียนใน Java คลาสการใช้งานคือ sun.misc.launcher $ appclassloader นอกจากนี้เรารู้ว่ามีวิธี getSystemClassLoader ใน classloader วิธีนี้ส่งคืน AppClassLoader.AppClassLoader ส่วนใหญ่รับผิดชอบในการโหลดเอกสารคลาสหรือ JAR ที่ตำแหน่งที่ระบุโดย ClassPath นอกจากนี้ยังเป็นตัวโหลดคลาสเริ่มต้นสำหรับโปรแกรม Java
รูปแบบการมอบหมายผู้ปกครอง
การโหลด classloader ใน Java ใช้กลไกผู้แทนหลัก เมื่อโหลดคลาสโดยใช้กลไกผู้ได้รับมอบหมายผู้ปกครองขั้นตอนต่อไปนี้จะถูกนำมาใช้:
ปัจจุบัน ClassLoader ตรวจสอบครั้งแรกว่าคลาสนี้ได้รับการโหลดจากคลาสที่โหลดไปแล้วหรือไม่ หากมีการโหลดแล้วมันจะส่งคืนคลาสเดิมโดยตรง
ตัวโหลดแต่ละคลาสมีแคชโหลดของตัวเอง เมื่อคลาสถูกโหลดมันจะถูกใส่ลงในแคชและสามารถส่งคืนโดยตรงเมื่อโหลดในครั้งต่อไป
เมื่อไม่พบแคชของ classloader ตัวโหลดคลาสแม่จะถูกมอบหมายให้โหลด ตัวโหลดคลาสแม่ใช้กลยุทธ์เดียวกัน ก่อนอื่นให้ตรวจสอบแคชของตัวเองจากนั้นมอบหมายคลาสพาเรนต์ของคลาสพาเรนต์ให้โหลดไปจนถึง bootstrp classloader
เมื่อตัวโหลดคลาสแม่ทั้งหมดไม่ได้โหลดพวกเขาจะถูกโหลดโดยตัวโหลดคลาสปัจจุบันและใส่ไว้ในแคชของตัวเองเพื่อให้สามารถส่งคืนโดยตรงในครั้งต่อไปที่มีคำขอโหลด
เมื่อพูดถึงสิ่งนี้คุณอาจสงสัยว่าทำไม Java จึงใช้กลไกการมอบหมายเช่นนี้? เพื่อให้เข้าใจถึงปัญหานี้เราได้แนะนำแนวคิดอื่น "เนมสเปซ" เกี่ยวกับ classloader ซึ่งหมายความว่าในการกำหนดคลาสที่แน่นอนคุณต้องมีชื่อที่มีคุณสมบัติครบถ้วนของคลาสและโหลดคลาสคลาสนี้เพื่อกำหนดร่วมกัน กล่าวคือแม้ว่าชื่อที่ผ่านการรับรองอย่างสมบูรณ์ของสองคลาสจะเหมือนกันเพราะคลาสโหลดที่แตกต่างกันโหลดคลาสนี้ แต่ก็เป็นคลาสที่แตกต่างกันใน JVM หลังจากทำความเข้าใจเนมสเปซให้ดูที่โมเดลผู้แทนกันเถอะ หลังจากใช้โมเดลตัวแทนความสามารถเชิงโต้ตอบของคลาสโหลดที่แตกต่างกันจะเพิ่มขึ้น ตัวอย่างเช่นดังที่ได้กล่าวไว้ข้างต้นไลบรารีคลาสที่จัดทำโดย JDK Binsheng ของเราเช่น HashMap, LinkedList ฯลฯ หลังจากคลาสเหล่านี้โหลดโดยตัวโหลดคลาส bootstrp ไม่ว่าจะมีตัวโหลดชั้นเรียนอยู่ในโปรแกรมของคุณ
วิธีปรับแต่ง classloader
นอกเหนือจาก classloader ที่จัดทำโดยค่าเริ่มต้นที่กล่าวถึงข้างต้นแล้ว Java ยังอนุญาตให้แอปพลิเคชันปรับแต่งคลาสโหลด หากคุณต้องการปรับแต่ง classloader เราจำเป็นต้องใช้งานโดยสืบทอด java.lang.classloader ต่อไปลองมาดูวิธีการสำคัญหลายประการที่เราต้องให้ความสนใจเมื่อปรับแต่ง ClassLoader:
1. วิธีการโหลดคลาส
วิธี loadclass ประกาศ
คลาสสาธารณะ <?> loadclass (ชื่อสตริง) พ่น classnotFoundException
ข้างต้นคือการประกาศต้นแบบของวิธีการโหลด การใช้กลไกการมอบหมายผู้ปกครองที่กล่าวถึงข้างต้นนั้นถูกนำไปใช้ในวิธีนี้ ลองมาดูรหัสของวิธีนี้เพื่อดูว่ามันใช้การมอบหมายผู้ปกครองอย่างไร
ใช้วิธีการโหลดคลาส
คลาสสาธารณะ <?> loadclass (ชื่อสตริง) พ่น classnotfoundexception {return loadclass (ชื่อ, false);}จากด้านบนเราจะเห็นว่าวิธีการโหลด LoadClass เรียกใช้วิธี loadcclass (ชื่อ, เท็จ) ดังนั้นลองมาดูการใช้วิธี loadclass อื่น
คลาส loadclass (ชื่อสตริง, บูลีนแก้ไข)
คลาสซิงโครไนซ์ที่ได้รับการป้องกัน <?> loadclass (ชื่อสตริง, การแก้ไขบูลีน) พ่น classnotfoundexception {// ก่อนตรวจสอบว่าคลาสได้รับการโหลดคลาสแล้ว c = findloadedclass (ชื่อ); // ตรวจสอบว่าคลาสได้รับการโหลดหรือไม่ (c == null) มีการระบุไว้แล้วตัวโหลดหลักจะถูกมอบหมายให้โหลด } else {c = findBootStrapClass0 (ชื่อ); // หากไม่มีตัวโหลดคลาสพาเรนต์ให้มอบตัวโหลด bootstrap เพื่อโหลด}} catch (classnotFoundException e) {// ถ้ายังไม่พบ c = findclass (ชื่อ); // หากการโหลดคลาสแม่ไม่ได้โหลดมันจะถูกโหลดผ่าน findclass ของตัวเอง }} if (Resolve) {ResolveClass (c);} return c;}ในรหัสข้างต้นฉันเพิ่มความคิดเห็นเพื่อดูว่ากลไกการมอบหมายผู้ปกครองของ LoadClass ทำงานอย่างไร สิ่งหนึ่งที่เราต้องทราบที่นี่คือคลาสสาธารณะ <?> loadclass (ชื่อสตริง) พ่น classnotfoundexception ไม่ได้ทำเครื่องหมายเป็นขั้นสุดท้ายซึ่งหมายความว่าเราสามารถแทนที่วิธีนี้ซึ่งหมายความว่ากลไกการมอบหมายผู้ปกครองอาจแตก นอกจากนี้เราสังเกตเห็นว่ามีวิธีการค้นหาด้านบน ถัดไปเรามาพูดกันว่าวิธีนี้เป็นวิธีที่ไม่ดีหรือไม่
2.FindClass
เราตรวจสอบซอร์สโค้ดของ java.lang.classloader และเราพบว่าการใช้งาน FindClass มีดังนี้:
คลาสที่ได้รับการป้องกัน <?> findClass (ชื่อสตริง) พ่น classnotFoundException {โยน classnotFoundException ใหม่ (ชื่อ);}เราจะเห็นได้ว่าการใช้งานเริ่มต้นของวิธีนี้คือการโยนข้อยกเว้นโดยตรง แต่อันที่จริงแล้ววิธีนี้จะถูกทิ้งไว้ในแอปพลิเคชันของเราเพื่อแทนที่ การใช้งานเฉพาะขึ้นอยู่กับตรรกะการใช้งานของคุณ คุณสามารถอ่านจากดิสก์หรือรับไฟล์ไบต์ของไฟล์คลาสจากเครือข่าย หลังจากได้รับไบนารีคลาสแล้วคุณสามารถส่งมอบให้กับ Defineclass สำหรับการโหลดเพิ่มเติม เรามาอธิบาย defineclass ในภายหลัง ตกลงผ่านการวิเคราะห์ข้างต้นเราสามารถวาดข้อสรุปต่อไปนี้:
เมื่อเราเขียน classloader ของเราเองหากเราต้องการติดตามกลไกการมอบหมายผู้ปกครองเราจะต้องแทนที่ FindClass เท่านั้น
3. แน่นอน
ก่อนอื่นให้ดูที่ซอร์สโค้ดของ defineclass:
กำหนด
คลาสสุดท้ายที่ได้รับการป้องกัน <?> defineclass (ชื่อสตริง, ไบต์ [] b, int ปิด, int len) พ่น classformaterror {return defineclass (ชื่อ, b, ปิด, len, null);}จากรหัสข้างต้นเราจะเห็นว่าวิธีนี้ถูกกำหนดให้เป็นขั้นสุดท้ายซึ่งหมายความว่าวิธีนี้ไม่สามารถแทนที่ได้ ในความเป็นจริงนี่เป็นรายการเดียวที่เหลือให้เราโดย JVM ผ่านรายการที่ไม่ซ้ำกันนี้ JVM ทำให้มั่นใจได้ว่าไฟล์คลาสจะต้องปฏิบัติตามคำจำกัดความของคลาสที่ระบุในข้อกำหนดของเครื่องเสมือน Java ในที่สุดวิธีนี้จะเรียกวิธีการดั้งเดิมเพื่อใช้การโหลดของคลาสจริง
ตกลงผ่านคำอธิบายข้างต้นลองนึกถึงคำถามต่อไปนี้:
หากเราเขียนคลาส java.lang.string ด้วยตัวเองเราสามารถแทนที่คลาสที่เรียก JDK ได้หรือไม่?
คำตอบคือไม่ เราไม่สามารถบรรลุเป้าหมายได้ ทำไม ฉันเห็นคำอธิบายออนไลน์มากมายว่ากลไกการมอบหมายผู้ปกครองช่วยแก้ปัญหานี้ได้ แต่จริงๆแล้วมันไม่ถูกต้องมากนัก เนื่องจากกลไกการมอบหมายผู้ปกครองสามารถหักได้คุณสามารถเขียน classloader เพื่อโหลดคลาส java.lang.string ที่คุณเขียน แต่คุณจะพบว่ามันจะไม่โหลดสำเร็จ โดยเฉพาะสำหรับชั้นเรียนที่เริ่มต้นด้วย Java.*การใช้งานของ JVM ทำให้มั่นใจได้ว่าจะต้องโหลดโดย bootstrp
สถานการณ์ที่ไม่ปฏิบัติตาม "กลไกการมอบหมายผู้ปกครอง"
ดังกล่าวข้างต้นว่ากลไกการมอบหมายผู้ปกครองส่วนใหญ่จะตระหนักถึงปัญหาการโต้ตอบของคลาสที่โหลดระหว่างคลาสโหลดที่แตกต่างกัน คลาสที่แบ่งปันโดยทุกคนจะถูกส่งมอบให้กับตัวโหลดหลักเพื่อโหลด แต่มีสถานการณ์ใน Java ที่คลาสที่โหลดโดยตัวโหลดคลาสแม่จำเป็นต้องใช้คลาสที่โหลดโดยตัวโหลดเด็ก มาพูดคุยเกี่ยวกับการเกิดขึ้นของสถานการณ์นี้
มีมาตรฐาน SPI (ServiceProviderInterface) ใน Java ที่ใช้ห้องสมุด SPI เช่น JDBC, JNDI ฯลฯ เราทุกคนรู้ว่า JDBC ต้องการไดรเวอร์ที่บุคคลที่สามจัดหาให้และแพ็คเกจ Jar ของคนขับถูกวางไว้ในคลาสของแอปพลิเคชันของเราเอง API ของ JDBC เองเป็นส่วนหนึ่งของ JDK ที่จัดทำโดย JDK และมันถูกโหลดโดย Bootstrp ดังนั้นฉันจะโหลดคลาสการใช้งานที่จัดทำโดยผู้ผลิตบุคคลที่สามได้อย่างไร Java แนะนำแนวคิดของการโหลดคลาสบริบทของเธรด ตัวโหลดคลาสเธรดจะสืบทอดจากเธรดพาเรนต์โดยค่าเริ่มต้น หากไม่ได้ระบุไว้ค่าเริ่มต้นคือตัวโหลดคลาสระบบ (AppClassLoader) ด้วยวิธีนี้เมื่อโหลดไดรเวอร์ของบุคคลที่สามสามารถโหลดได้ผ่านตัวโหลดคลาสบริบทของเธรด
นอกจากนี้เพื่อที่จะใช้ osgi โหลดคลาสที่ยืดหยุ่นมากขึ้นและ javaappservers บางส่วนมันยังทำลายกลไกการมอบหมายหลัก
สรุป
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้เกี่ยวกับการวิเคราะห์รหัสของการใช้งานและการใช้กลไก Java Classloader ฉันหวังว่ามันจะเป็นประโยชน์กับทุกคน เพื่อนที่สนใจสามารถอ้างถึงหัวข้ออื่น ๆ ที่เกี่ยวข้องในเว็บไซต์นี้ต่อไป หากมีข้อบกพร่องใด ๆ โปรดฝากข้อความไว้เพื่อชี้ให้เห็น ขอบคุณเพื่อนที่ให้การสนับสนุนเว็บไซต์นี้!