從事java的小夥伴你們好,若是你是一名從事java行業的程序員,不管你是小白仍是工做多年的老司機,我相信這篇文章必定會給你帶來java
不一樣程度的收貨不敢說你看完個人文章今後精通jvm打遍天下無對手,但我能保證的是看完個人文章而且實踐操做加以理解,至少在jvm程序員
的這個領域碾壓百分之80以上的程序員。廢話很少說咱們進入正題。(此文連載,請持續關注!!!)面試
我相信不少從事java的小夥伴兒在網上或者視頻上也看了不少關於jvm的文章和講解,但總以爲缺乏點兒什麼,那麼今天我來告訴你爲何數據庫
會有這種感受,由於不少人或者說不少文章講的jvm都沒有從最底層,沒有從人的習慣性思惟上去剖析。從今天開始,我就要把jvm那點兒bootstrap
事兒給小夥伴兒們說清楚。保證你會見到jvm領域的另外一番美妙的天地。api
廢話很少說直接進入正題。講類加載器以前,我們先從類加載開始提及,那麼java底層是如何加載一個類的呢,他的順序是怎麼樣的呢?下面數組
我會細緻的手把手帶你分析。安全
在java代碼中,類型的加載,鏈接,初始化過程都是在程序運行期間完成的.微信
答:類型就是指的咱們Java源代碼經過編譯後的class文件網絡
答:
1:本地磁盤
2:網絡下載.class文件
3:war,jar下加載.class文件
4:從專門的數據庫中讀取.class文件(少見)
5:將java源文件動態編譯成class文件
6:典型的就是動態代理,經過運行期生成class文件
7:咱們的jsp會被轉換成servlet,而咱們的serlvet是一個java文件,會被編譯成class文件
答:經過咱們的類加載器(classLoader)進行加載
1:啓動類加載器 (Bootstrap Classloader)
2:擴展類加載器(Extention ClassLoader)
3:應用類加載器
1:自定義類加載器
接下來咱們一個一個類加載剖析,好好的講講他們的前世此生,以及做用,讓你完全的瞭解類加載器究竟是什麼。
加載的職責:負責加載JAVA_HOME/jre/lib/rt.jar。該加載器是有C++實現,不是ClassLoader類的子類。
接下來咱們會以代碼穿插的方式來說清楚類加載器究竟是什麼。不少人只是用嘴說,這樣當時聽懂了,然而並不能
真正的體會到類加載器以及類加載的精髓。只有用示例去驗證你的說法,這纔是正確的學習姿式。廢話很少說開搞。
首先建立一個java工程 jvm-classloader jdk版本採用1.8.0_144 包名爲src/com.test 建立類 MainClass01,結構以下圖:
首先咱們經過代碼的形式來看看,咱們的啓動類加載器到底加載了哪些包
package com.test; import java.util.Arrays; import java.util.List; /** * jvm 類加載器 第一章 * @author 奇客時間-時光 * 打印啓動類加載器加載的路徑 */ public class MainClass01 { public static void main(String[] args) { String bootStrapLoadingPath = System.getProperty("sun.boot.class.path"); List<String> bootLoadingPathList = Arrays.asList(bootStrapLoadingPath.split(";")); for(String bootPath:bootLoadingPathList) { System.out.println("啓動類加載器加載的目錄:{}"+bootPath); } } } "C:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=60329:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;I:\jvm\out\production\jvm-classloader" com.test.MainClass01 啓動類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar 啓動類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar 啓動類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\sunrsasign.jar 啓動類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar 啓動類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar 啓動類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar 啓動類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar 啓動類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\classes Process finished with exit code 0
經過以上運行代碼可知,啓動類加載器加載以下的這些jar包。包含了你們常說的rt.jar。那麼如今有個問題,從運行結果上咱們看啓動類加載加載了C:\Program Files\Java\jdk1.8.0_144\jre\classes這個目錄下的類。那麼咱們本身寫一個類,編譯成Class文件之後丟到這個目錄下它會被加載嘛?咱們不妨來試一下。咱們在com.test包下面建立一個Cat類。而後找到編譯好的Cat.class文件,扔到C:\Program Files\Java\jdk1.8.0_144\jre\classes目錄下。
如圖可知,咱們jre目錄並無classes目錄,因此咱們手動建立一個,而且把Cat.class文件扔進去。而後在測試類中獲取一下Cat.classLoader是什麼,由此就能夠知道Cat是否是被啓動類加載器加載了。
由上面的運行結果可知,咱們的Cat類確實被啓動類加載器給加載了。爲何是null?當一個類被啓動類加載器加載之後,那麼他的getClassLoader返回的結果就是null,這也是啓動類加載器和其餘類加載器不同的地方。
補充說明:Bootstrap Classloader加載器是由C++去加載的,而後Bootstrap Classloader加載rt等jar包,Bootstrap Classloader加載器也加載了系統類加載器和擴展類類加載器他們都位於sun.boot.class.path這個路徑。
加載的職責:加載java平臺擴展的jar包,\lib\ext,能夠經過-Djava.ext.dirs指定加載的路徑
該加載器是有java代碼編寫的,而且是ClassLoader的子類,位於sun.misc.Launcher$ExtClassLoader 是咱們launch類的一個內部類
一樣的咱們在MainClass01中測試一下
package com.test; import java.util.Arrays; import java.util.List; /** * jvm 類加載器 第一章 * @author 奇客時間-時光 * 打印啓動類加載器加載的目錄 * 打印擴展類加載器加載的目錄 */ public class MainClass01 { public static void main(String[] args) { String extLoadingPath = System.getProperty("java.ext.dirs"); List<String> list = Arrays.asList(extLoadingPath.split(";")); for(String extpath:list) { System.out.println("擴展類加載器加載的目錄:{}"+extpath); } } } "C:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=59761:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;I:\jvm\out\production\jvm-classloader" com.test.MainClass01 擴展類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext 擴展類加載器加載的目錄:{}C:\WINDOWS\Sun\Java\lib\ext Process finished with exit code 0
加載的職責:負責加載咱們工程目錄下classpath下的文件下.class以及jar包
該加載器也是由Java代碼編寫的也是咱們ClassLoader的一個子類,sun.misc.Launcher$AppClassLoader 是咱們的launcher的內部類
String appLoadingPath = System.getProperty("java.class.path");
一樣的,咱們在MainClass01中運行以下代碼:
package com.test; import java.util.Arrays; import java.util.List; /** * jvm 類加載器 第一章 * @author 奇客時間-時光 * 打印啓動類加載器加載的目錄 * 打印擴展類加載器加載的目錄 * 打印應用類加載器加載的目錄 */ public class MainClass01 { public static void main(String[] args) { String appLoadingPath = System.getProperty("java.class.path"); List<String> appLoadingPathList = Arrays.asList(appLoadingPath.split(";")); for(String appPath:appLoadingPathList) { System.out.println("應用類加載器加載的目錄:{}"+appPath); } } } "C:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=52479:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;I:\jvm\out\production\jvm-classloader" com.test.MainClass01 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar 應用類加載器加載的目錄:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar 應用類加載器加載的目錄:{}I:\jvm\out\production\jvm-classloader 應用類加載器加載的目錄:{}C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar Process finished with exit code 0
由此可知,三種類加載所加載對應的目錄已經介紹完畢,後面還會對這三種類加載器進行深刻的解析。下面咱們再看看自定義類加載器。爲何須要自定義類加載器呢?由於有些場景下jdk給咱們提供的三種類加載器沒有辦法實現咱們的個性化需求。關於爲何,這個我後面會詳細闡述。咱們首先看看自定義類加載器以及源碼。
如何寫自定義類加載器呢?首先咱們看看jdk 提供的java原版 doc文檔是如何描述的,這裏要說一下,不管各位小夥伴之後學什麼技術,第一手資料很重要。由於是最權威的,也沒有通過任何加工的,也是最準確的。因此咱們直接看源碼中的doc文檔便可。
那麼咱們要寫本身的類加載器,對於小白來講確定是不清楚如何下手的。這個時候,咱們就要想,自定義類加載確定是和類加載器有關係,不放咱們看看java有沒有提供關於類加載器相關的類。因而咱們能夠經過IDEA的編輯器進行搜索一下,類加載器顧名思義 ClassLoader先搜索一下。結果還真有。說明這是一個跟類加載器有關的類,咱們直接閱讀他的java doc。
我把以前編輯好的中文註釋貼在下面。你們能夠閱讀,不過這裏仍是建議學習jdk源碼的小夥伴仍是本身閱讀比較好,這樣記得也比較深入。
/** * A class loader is an object that is responsible for loading classes(類加載器是一個對象,做用就是用來加載器類的). The * class ClassLoader is an abstract class(本類是一個抽象的類). Given the binary name of a class (給定一個類的二進制名字,好比java.lang.String就是String類的二進制名字) , a class loader should attempt to * locate or generate data that constitutes a definition for the class(那麼類加載器嘗試去定位(已經存在咱們的磁盤文件中) 或者生成(爲啥有生成,存在動態代理)構成class的定義數據). A typical strategy is to transform the name into a file name and then read a * "class file" of that name from a file system.(一般的策略就是把咱們的類的二進制名稱轉換爲咱們的文件名稱 好比java.lang.String 轉換爲java/lang/String) 那麼就會根據這個文件名稱去文件系統找到該class文件。 * * <p> Every {@link Class <tt>Class</tt>} object contains a {@link * Class#getClassLoader() reference} to the <tt>ClassLoader</tt> that defined * it. (每個經過類加載器加載的class文件後會返回一個Class對象,該Class對象會包含一個加載他的ClassLoader的對象引用) * * <p> <tt>Class</tt> objects for array classes are not created by class * loaders, but are created automatically as required by the Java runtime. 咱們的數組對象的Class對象不是由咱們的類加載器建立的,而是由咱們的jvm根據須要在運行時間建立出來的. * The class loader for an array class, as returned by {@link * Class#getClassLoader()} is the same as the class loader for its element * type;(咱們獲取到了數組類型的CLass對象,經過該Class對象調用getClassLoader()返回的是跟咱們數組元素中的類加載器是同樣的) if the element type is a primitive type, then the array class has no * class loader.(若是咱們原生的數組 那麼該數組的Class沒有類加載器) * <p> Applications implement subclasses of <tt>ClassLoader</tt> in order to * extend the manner in which the Java virtual machine dynamically loads * classes.(咱們經過實現ClassLoader,能夠動態來擴展咱們類加載的方式) * <p> Class loaders may typically be used by security managers to indicate * security domains. * <p> The <tt>ClassLoader</tt> class uses a delegation model to search for * classes and resources.(ClassLoader使用雙親委派模型來尋找類和資源) Each instance of <tt>ClassLoader</tt> has an * associated parent class loader(classloader的每個實例都會有一個parent的classLoader). When requested to find a class or * resource, a <tt>ClassLoader</tt> instance will delegate the search for the * class or resource to its parent class loader before attempting to find the * class or resource itself(當咱們發起類加載的請求,那麼類加載器本身去尋找資源以前委託給父類). The virtual machine's built-in class loader, * called the "bootstrap class loader", (虛擬機內嵌的classLoader是叫作啓動類加載器)does not itself have a parent but may * serve as the parent of a <tt>ClassLoader</tt> instance.(啓動類加載器是沒有雙親的,可是他能夠做爲其餘類加載器的雙親) * <p> Class loaders that support concurrent loading of classes are known as * <em>parallel capable</em> class loaders(若一個類加載器支持並行加載的話,那麼這個類加載器就叫作並行類加載器) and are required to register * themselves at their class initialization time by invoking the * {@link * #registerAsParallelCapable <tt>ClassLoader.registerAsParallelCapable</tt>} * method.(若一個類加載器想要成爲一個並行類加載器的話,那麼在該類加載器初始化的時候要求去調用ClassLoader.registerAsParallelCapable方法) Note that the <tt>ClassLoader</tt> class is registered as parallel * capable by default(默認狀況下當前的這個類ClassLoader這個抽象類模式是並行加載器器). However, its subclasses still need to register themselves * if they are parallel capable.(然而咱們的子加載器須要成爲並行的內加載器,須要註冊本身爲並行的類加載器) <br> * In environments in which the delegation model is not strictly * hierarchical(若咱們的類加載器不是屬於雙親委派的模型狀況下), class loaders need to be parallel capable(類加載器須要註冊爲並行的加載器), otherwise class * loading can lead to deadlocks because the loader lock is held for the * duration of the class loading process (see {@link #loadClass * <tt>loadClass</tt>} methods).(否則在內加載的階段會致使死鎖) * <p> Normally, the Java virtual machine loads classes from the local file * system in a platform-dependent manner(一般狀況下 jvm從本地磁盤下去加載類). For example, on UNIX systems, the * virtual machine loads classes from the directory defined by the * <tt>CLASSPATH</tt> environment variable.(在unix系統中,虛擬機從classpath下加載類) * <p> However, some classes may not originate from a file(然而,有些class文件不是存在咱們的磁盤文件中); they may originate * from other sources, such as the network(好比從網絡上), or they could be constructed by an * application(動態代理生產的). The method {@link #defineClass(String, byte[], int, int) * <tt>defineClass</tt>} converts an array of bytes into an instance of class * <tt>Class</tt>. (咱們的defineClass方法會把字節數組轉爲咱們一個class的實例)Instances of this newly defined class can be created using * {@link Class#newInstance <tt>Class.newInstance</tt>}.這個實例能夠經過咱們的newInstance來調用 全盤委託模型:由咱們classloader加載出來的class那麼該類中的方法或構造函數可能引用其餘類,jvm會調用同一個類加載器去加載被應用的類 * <p> The methods and constructors of objects created by a class loader may * reference other classes(). To determine the class(es) referred to, the Java * virtual machine invokes the {@link #loadClass <tt>loadClass</tt>} method of * the class loader that originally created the class. * <p> For example, an application could create a network class loader to * download class files from a server. Sample code might look like: * <blockquote><pre> * ClassLoader loader = new NetworkClassLoader(host, port); * Object main = loader.loadClass("Main", true).newInstance(); * . . . * </pre></blockquote> 咱們自定義的class laoder 須要重寫咱們的ClassLoader類的findClass 和咱們的loadClassData方法 * <p> The network class loader subclass must define the methods {@link * #findClass <tt>findClass</tt>} and <tt>loadClassData</tt> to load a class * from the network. Once it has downloaded the bytes that make up the class, * it should use the method {@link #defineClass <tt>defineClass</tt>} to * create a class instance. A sample implementation is: * <blockquote><pre> * class NetworkClassLoader extends ClassLoader { * String host; * int port; * public Class findClass(String name) { * byte[] b = loadClassData(name); * return defineClass(name, b, 0, b.length); * } * private byte[] loadClassData(String name) { * // load the class data from the connection * . . . * } * } * </pre></blockquote> * <h3> <a name="name">Binary names</a> </h3> * <p> Any class name provided as a {@link String} parameter to methods in * <tt>ClassLoader</tt> must be a binary name as defined by * <cite>The Java™ Language Specification</cite>. * <p> Examples of valid class names include: * <blockquote><pre> * "java.lang.String" 表示咱們的String類的二進制名稱 * "javax.swing.JSpinner$DefaultEditor" 表示JSpinner的內部類DefaultEditor的二進制名稱 * "java.security.KeyStore$Builder$FileBuilder$1" 表示java.security.KeyStore類的內部類Builder類的內部類的FileBuilder的第一個內部類 * "java.net.URLClassLoader$3$1" 表示java.net.URLClassLoader類中第三個內部類中的第一個內部類 * </pre></blockquote> * @see #resolveClass(Class) * @since 1.0 */ public abstract class ClassLoader {}
經過上面文檔的描述咱們能夠實現ClassLoader這個類就能夠動態來擴展咱們類加載的方式。從上面的文檔中也能夠看出,加載一個class文件須要用到loadClass方法,而且還要重寫ClassLoader類的findClass 和咱們的loadClassData方法
接下來對上述的幾個重要的方法的java doc進行翻譯
/** * Loads the class with the specified <a href="#name">binary name</a>. (根據二進制名稱進行加載) The * default implementation of this method searches for classes in the(下面是調用方法的順序) * following order: * <ol> * <li><p> Invoke {@link #findLoadedClass(String)} to check if the class * has already been loaded. </p></li> 調用findloaderClass檢查類是否被加載過 * <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method * on the parent class loader(首先會調用父類的loaderClass來加載). If the parent is <tt>null</tt> the class * loader built-in to the virtual machine is used(若發現父類是null,那就說明已經到了頂層的啓動類加載器), instead. </p></li> * <li><p> Invoke the {@link #findClass(String)} method to find the * class. </p></li> 接下來就調用咱們的findClass方法來查找class * </ol> * <p> If the class was found using the above steps(若經過上述步驟找到了class文件), and the * <tt>resolve</tt> flag is true(那麼就設置resolve爲true), this method will then invoke the {@link * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.(解析來那麼就經過調用resolverClass 返回對象) * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link * #findClass(String)}, rather than this method. </p> 強烈的要求咱們子類實現ClassLoader 那麼咱們必需要從寫findClass方法 * <p> Unless overridden, this method synchronizes on the result of * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method * during the entire class loading process. * @param name * The <a href="#name">binary name</a> of the class * @param resolve * If <tt>true</tt> then resolve the class * @return The resulting <tt>Class</tt> object * @throws ClassNotFoundException * If the class could not be found */ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { //檢查根據二進制名稱是否被加載過 Class<?> c = findLoadedClass(name); //沒有加載過 if (c == null) { long t0 = System.nanoTime(); try { // 判斷有沒有父類,有父類 調用父類的loadClass if (parent != null) { c = parent.loadClass(name, false); } else { //沒有父類,表面當前的類加載器已經到了啓動類加載器了,那麼經過啓動類加載器去找 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } //還沒找到 if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); //調用findClass去找 c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { //解析咱們的class進行鏈接 等操做 resolveClass(c); } return c; } }
上面這段java doc文檔中有幾個重要的點須要注意一下
1:根據二進制名稱進行加載
2:調用findloaderClass檢查類是否被加載過
3:首先會調用父類的loaderClass來加載
4:若發現父類是null,那就說明已經到了頂層的啓動類加載器
5:接下來就調用咱們的findClass方法來查找class
這幾部是類加載器加載類的加載過程,比較重要的過程。但願各位小夥伴兒必定要結合源碼牢記。
上述又提到了另外一個方法
/** * Finds the class with the specified <a href="#name">binary name</a>.經過給定的二進制名稱查找出class文件 * This method should be overridden by class loader implementations(這個方法應該被遵循雙親委託模型的子類重寫) that * follow the delegation model for loading classes(), and will be invoked by * the {@link #loadClass <tt>loadClass</tt>} method after checking the * parent class loader for the requested class(這個方法會被loadClass方法調用,在父類加載器檢查了是否加載事後). The default implementation * throws a <tt>ClassNotFoundException</tt>. * * @param name * The <a href="#name">binary name</a> of the class * * @return The resulting <tt>Class</tt> object * * @throws ClassNotFoundException * If the class could not be found * * @since 1.2 */ protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
/** 這個方法的做用就是用來把字節數組轉爲一個Class對象,這個對象 * Converts an array of bytes into an instance of class <tt>Class</tt>. * Before the <tt>Class</tt> can be used it must be resolved(這個Class對象能被使用必須是通過解析的由於在解析階段會進行驗證). This method * is deprecated in favor of the version that takes a <a * href="#name">binary name</a> as its first argument, and is more secure. 就是不推薦用這個方法,而是 推薦用它重寫的方法,由於重寫方法安全些(由於重寫方法中一個參數是咱們的是傳入二進制模型,他是經過安全域進行保護的) * * @param b * The bytes that make up the class data. The bytes in positions * <tt>off</tt> through <tt>off+len-1</tt> should have the format * of a valid class file as defined by * <cite>The Java™ Virtual Machine Specification</cite>. * * @param off * The start offset in <tt>b</tt> of the class data * * @param len * The length of the class data * * @return The <tt>Class</tt> object that was created from the specified * class data * * @throws ClassFormatError * If the data did not contain a valid class * * @throws IndexOutOfBoundsException * If either <tt>off</tt> or <tt>len</tt> is negative, or if * <tt>off+len</tt> is greater than <tt>b.length</tt>. * * @throws SecurityException * If an attempt is made to add this class to a package that * contains classes that were signed by a different set of * certificates than this class, or if an attempt is made * to define a class in a package with a fully-qualified name * that starts with "{@code java.}". * * @see #loadClass(String, boolean) * @see #resolveClass(Class) * * @deprecated Replaced by {@link #defineClass(String, byte[], int, int) * defineClass(String, byte[], int, int)} */ @Deprecated protected final Class<?> defineClass(byte[] b, int off, int len) throws ClassFormatError { return defineClass(null, b, off, len, null); }
以上兩個方法在註釋中均已經解釋。
看完以上的java doc文檔,那麼咱們就能夠來寫一個自定義的類加載器了。
建立自定義類加載的步驟以下:
1:建立一個類名爲Test01ClassLoader,這個是咱們本身的類加載器
2:繼承ClassLoader類,而且重寫findClass方法。
3:建立方法loadClassData構建一個byte[]數組。
4:在findClass中調用父類的defineClass方法,傳入相關信息(參數name是名稱,傳入構建好的byte數組,起始下標從0開始,最後是數組長度)
5:構造函數不能少。若是帶參數,意爲你指定雙親加載器是誰(類加載器雙親委託模型後面文章中介紹,先記住自定義類加載器的寫法和步驟便可)
6:最後就是在main函數中用咱們自定義的類加載器去加載咱們指定的類來測試。。。
package com.test; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; /** * jvm 類加載器 第一章 * @author 奇客時間-時光 * 自定義類加載器 * 1,繼承ClassLoader * 2,經過構造器指定當前類加載器的父加載器 * 3,重寫findClass方法 * 4,構造一個byte[]字數 * 5,調用this.defineClass方法建立classLoader的實例 */ public class Test01ClassLoader extends ClassLoader { private String classLoaderName; //聲明文件後綴 private final String x = ".class"; //聲明文件路徑 private String path; public void setPath(String path) { this.path = path; } /** * 使用默認的類加載器做爲當前類加載器的雙親{APPClassLoader做爲雙親} * @param classLoaderName */ public Test01ClassLoader(String classLoaderName){ super();//指定系統類加載器爲咱們的父加載器 this.classLoaderName = classLoaderName; } /** * 使用咱們本身指定的類加載器做爲當前類加載器的雙親 * @param parentClassLoaderNane * @param classLoaderName */ public Test01ClassLoader(ClassLoader parentClassLoaderNane,String classLoaderName){ super(parentClassLoaderNane); this.classLoaderName = classLoaderName; } /** * 重寫findClass方法,這一步很是重要必不可少 * @param name * @return * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { System.out.println("本身的類加載器被加載了"); byte[] bytes = this.loadClassData(name); return this.defineClass(name,bytes,0,bytes.length); } /** * 返回一個字節數字經過給定的類名 * @param name * @return */ private byte[] loadClassData(String name ){ InputStream inputStream = null; byte[] bytes = null; ByteArrayOutputStream byteArrayOutputStream = null; try { name = name.replace(".","\\"); inputStream= new FileInputStream(this.path+new File(name+this.x)); byteArrayOutputStream = new ByteArrayOutputStream(); int ch = 0; while(-1 !=(ch= inputStream.read())){ byteArrayOutputStream.write(ch); } bytes=byteArrayOutputStream.toByteArray(); }catch (Exception e){ e.printStackTrace(); }finally { try { inputStream.close(); byteArrayOutputStream.close(); }catch (Exception e){ e.printStackTrace(); } } return bytes; } public static void main(String[] args) throws Exception { //建立自定義類加載器的一個實例,而且經過構造器指定名稱 Test01ClassLoader myClassLoader = new Test01ClassLoader("loader1"); //設置加載路徑 myClassLoader.setPath("I:\\jvm\\out\\production\\jvm-classloader\\"); //調用loadClass方法來加載咱們的class文件 Class<?> classz = myClassLoader.loadClass("com.test.Dog"); //經過構造器建立實例 Object object = classz.getDeclaredConstructor().newInstance(); //查看咱們建立的實例是由哪一個類加載器加載的 System.out.println(object.getClass().getClassLoader()); /*myClassLoader.setPath("D:\\classes\\"); Class classz1 = myClassLoader.loadClass("com.jdyun.jvm05.Test"); Object object1 = classz1.getDeclaredConstructor().newInstance(); System.out.println(classz1.getClassLoader()); */ /*System.out.println(System.getProperty("sun.boot.class.path")); System.out.println(System.getProperty("java.ext.dirs")); System.out.println(System.getProperty("java.class.path")); System.out.println(Test12.getSystemClassLoader());*/ /*MyTest02[] list = new MyTest02[1]; System.out.println(list.getClass().getClassLoader());*/ } }
上述代碼運行結果顯示仍是經過AppClassLoader來加載的Dog類。那麼這是爲何呢?由於咱們在構造函數中指定的將系統類加載器也就是咱們的AppClassLoader做爲咱們的雙親,那麼根據雙親委託模型,父加載器能加載的類,就由父加載器來加載,因此咱們自定義的類加載沒有加載到Dog類。(雙親委託模型後面文章會詳細介紹。先記住這個結論就好。)
接下來咱們對代碼進行一點兒改造,來測試咱們自定義的類加載是否生效了。
我在G:\jdyun-jvm\out\production\jdyun-jvm\\com\\jdyun\\jvm05\\目錄下建立一個class文件Test.class,運行結果以下圖所示
我把加載的路徑改變,這樣咱們咱們的AppClassloader他只會加載classPath路徑下的文件,而咱們在外部指定的文件,就天然而然的被咱們的自定義類加載器給加載了。
接下來咱們對代碼進行一下小小的改動,咱們建立兩個自定義類加載器的實例,而後看看加載出來的Class對象是否是同樣的。
public static void main(String[] args) throws Exception { //建立自定義類加載器的一個實例,而且經過構造器指定名稱 Test01ClassLoader myClassLoader = new Test01ClassLoader("loader1"); //設置加載路徑 myClassLoader.setPath("G:\\jdyun-jvm\\out\\production\\jdyun-jvm\\"); //調用loadClass方法來加載咱們的class文件 Class<?> classz = myClassLoader.loadClass("com.jdyun.jvm05.Test"); //經過構造器建立實例 Object object = classz.getDeclaredConstructor().newInstance(); //查看咱們建立的實例是由哪一個類加載器加載的 System.out.println(classz.getClassLoader()); System.out.println(classz.hashCode()); //建立自定義類加載器的一個實例,而且經過構造器指定名稱 Test01ClassLoader myClassLoader2 = new Test01ClassLoader("loader1"); //設置加載路徑 myClassLoader2.setPath("G:\\jdyun-jvm\\out\\production\\jdyun-jvm\\"); //調用loadClass方法來加載咱們的class文件 Class<?> classz2 = myClassLoader2.loadClass("com.jdyun.jvm05.Test"); //經過構造器建立實例 Object object2 = classz2.getDeclaredConstructor().newInstance(); //查看咱們建立的實例是由哪一個類加載器加載的 System.out.println(classz2.getClassLoader()); System.out.println(classz2.hashCode()); } 打印結果: 本身的類加載器被加載了 com.test.Test01ClassLoader@1540e19d 21685669 本身的類加載器被加載了 com.test.Test01ClassLoader@7f31245a 325040804
經過上面代碼可知,同一個類加載器加載的同一個Class文件加載出來的Class對象不是同一個。而且兩個class對象之間也是不能相互轉換的。
好了各位小夥伴兒,第一篇文章我們就先介紹到這裏,你們如今瞭解一下4種類加載器便可,此專題的文章後面還會陸續連載,期待各位小夥伴的閱讀。
另外筆者在公衆號:奇客時間,給你們收錄了1000多道今年互聯網公司的面試真題
面試真題-回覆關鍵字形式:公司-部門-面試輪次,例如:阿里-螞蟻金服-一面,自動回覆面試真題;當前已經收錄以下: 字節跳動-抖音-面試輪次, 搜狐-搜索組-面試輪次, OPPO-商城-面試輪次, 58同城-基礎架構部-面試輪次,湖南臺-芒果TV-面試輪次 , 騰訊-乘車碼-面試輪次 , 騰訊-微信支付-面試輪次 , 騰訊-零售新業務-面試輪次 , 騰訊-直播平臺-面試輪次, 快手-廣告業務部-面試輪次 , 貝殼找房-商品組-面試輪次 , 百度-信息流-面試輪次 , 京東-零售-面試輪次 , 京東-物流-面試輪次 , 京東-電商-面試輪次 , 滴滴-小桔車服-面試輪次 , 滴滴-金融-面試輪次 , 阿里-高德-面試輪次 , 阿里-大文娛-面試輪次 , 阿里-健康-面試輪次 , 阿里-螞蟻金服-面試輪次 , 美團-外賣-面試輪次 , 美團-風控-面試輪次