引言
從事java的小伙伴大家好,如果你是一名從事java行業的程式員,無論你是小白還是作業多年的老司機,我相信這篇文章一定會給你帶來
不同程度的識訓不敢說你看完我的文章從此精通jvm打遍天下無對手,但我能保證的是看完我的文章并且實踐操作加以理解,至少在jvm
的這個領域碾壓百分之80以上的程式員,廢話不多說我們進入正題,(此文連載,請持續關注!!!)
一:類加載
我相信很多從事java的小伙伴兒在網上或者視頻上也看了很多關于jvm的文章和講解,但總覺得缺少點兒什么,那么今天我來告訴你為什么
會有這種感覺,因為很多人或者說很多文章講的jvm都沒有從最底層,沒有從人的習慣性思維上去剖析,從今天開始,我就要把jvm那點兒
事兒給小伙伴兒們說清楚,保證你會見到jvm領域的另一番美妙的天地,
廢話不多說直接進入正題,講類加載器之前,咱們先從類加載開始說起,那么java底層是如何加載一個類的呢,他的順序是怎么樣的呢?下面
我會細致的手把手帶你分析,
在java代碼中,型別的加載,連接,初始化程序都是在程式運行期間完成的.
型別加載:這里的型別指的的是什么呢?
答:型別就是指的我們Java源代碼通過編譯后的class檔案
型別的來源有哪些?型別的來源我總結了以下7點內容
答:
1:本地磁盤
2:網路下載.class檔案
3:war,jar下加載.class檔案
4:從專門的資料庫中讀取.class檔案(少見)
5:將java源檔案動態編譯成class檔案
6:典型的就是動態代理,通過運行期生成class檔案
7:我們的jsp會被轉換成servlet,而我們的serlvet是一個java檔案,會被編譯成class檔案
那么我們的是通過什么進行加載的呢?是如何被加載到jvm中的呢?
答:通過我們的類加載器(classLoader)進行加載
首先我們來了解以下類加載器的種類?系統級別的類加載器如下
1:啟動類加載器 (Bootstrap Classloader)
2:擴展類加載器(Extention ClassLoader)
3:應用類加載器
非系統級別類加載器如下:
1:自定義類加載器
接下來我們一個一個類加載剖析,好好的講講他們的前世今生,以及作用,讓你徹底的了解類加載器到底是什么,
一:啟動類加載器(Bootstrap Classloader)重點剖析
加載的職責:負責加載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這個路徑,
二:擴展類加載器(Extention ClassLoader)重點剖析
加載的職責:加載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
三:應用類加載器(APP ClassLoader)重點剖析
加載的職責:負責加載我們工程目錄下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原始碼的小伙伴還是自己閱讀比較好,這樣記得也比較深刻,
4.1:ClassLoader抽象類 檔案解讀
/**
* 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進行翻譯
4.2:我們的ClassLoader類加載器重要方法詳解
- loadClass方法詳解
/**
* Loads the class with the specified <a href="https://www.cnblogs.com/tomakemyself/p/#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="https://www.cnblogs.com/tomakemyself/p/#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
這幾部是類加載器加載類的加載程序,比較重要的程序,希望各位小伙伴兒一定要結合原始碼牢記,
上述又提到了另一個方法
- findClass()方法
/**
* Finds the class with the specified <a href="https://www.cnblogs.com/tomakemyself/p/#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="https://www.cnblogs.com/tomakemyself/p/#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);
}
- defineClass()方法
/**
這個方法的作用就是用來把位元組陣列轉為一個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="https://www.cnblogs.com/tomakemyself/p/#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-面試輪次 , 騰訊-乘車碼-面試輪次 , 騰訊-微信支付-面試輪次 , 騰訊-零售新業務-面試輪次 , 騰訊-直播平臺-面試輪次, 快手-廣告業務部-面試輪次 , 貝殼找房-商品組-面試輪次 , 百度-資訊流-面試輪次 , 京東-零售-面試輪次 , 京東-物流-面試輪次 , 京東-電商-面試輪次 , 滴滴-小桔車服-面試輪次 , 滴滴-金融-面試輪次 , 阿里-高德-面試輪次 , 阿里-大文娛-面試輪次 , 阿里-健康-面試輪次 , 阿里-螞蟻金服-面試輪次 , 美團-外賣-面試輪次 , 美團-風控-面試輪次
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/192825.html
標籤:Java
