Java基础:类加载器
系列閱讀
- Java基礎:類加載器
- Java基礎:反射
- Java基礎:注解
- Java基礎:動態代理
1. 什么是類加載器
類加載器就是用來加載類的東西!類加載器也是一個類:ClassLoader
類加載器可以被加載到內存,是通過類加載器完成的!Java虛擬機中可以安裝多個類加載器,系統默認三個主要類加載器,每個類負責加載特定位置的類:
- BootStrap:引導類加載器,加載rt.jar中的類
- ExtClassLoader:擴展類加載器,加載lib/ext目錄下的類
- AppClassLoader:系統類加載器,加載CLASSPATH下的類,即我們寫的類,以及第三方提供的類
類加載器之間存在上下級關系,系統類加載器的上級是擴展類加載器,而擴展類加載器的上級是引導類加載器
類加載器也是Java類,因為其它java類的類加載器本身也要被類加載器加載,顯然必須有第一個類加載器不是java類,這正是BootStrap。
Java虛擬機中的所有類裝載器采用具有父子關系的樹形結構進行組織,在實例化每個類裝載器對象時,需要為其指定一個父級類裝載器對象或者默認采用系統類裝載器為其父級類加載
2. 類的加載
當程序要使用某個類時,如果該類還未被加載到內存中,則系統會通過加載,連接,初始化三步來實現對這個類進行初始化。
2.1 加載
就是指將class文件讀入內存,并為之創建一個Class對象。任何類被使用時系統都會建立一個Class對象。
2.2 連接
- 驗證:是否有正確的內部結構,并和其他類協調一致
- 準備:負責為類的靜態成員分配內存,并設置默認初始化值
- 解析:將類的二進制數據中的符號引用替換為直接引用
2.3 初始化
類會在首次被“主動使用”時執行初始化,為類(靜態)變量賦予正確的初始值。在Java代碼中,一個正確的初始值是通過類變量初始化語句或者靜態初始化塊給出的。
初始化一個類包括兩個步驟:
- 如果類存在直接父類的話,且直接父類還沒有被初始化,則先初始化其直接父類
- 如果類存在一個初始化方法,就執行此方法
注:初始化接口并不需要初始化它的父接口。
3. 類初始化時機
- 創建類的實例
- 訪問類的靜態變量,或者為靜態變量賦值
- 調用類的靜態方法
- 使用反射方式來強制創建某個類或接口對應的java.lang.Class對象
- 初始化某個類的子類
- 直接使用java.exe命令來運行某個主類
4. 類加載器
負責將.class文件加載到內在中,并為之生成對應的Class對象。雖然我們不需要關心類加載機制,但是了解這個機制我們就能更好的理解程序的運行。
4.1 類加載器的組成
- BootstrapClassLoader 根類加載器
- ExtensionClassLoader 擴展類加載器
- SysetmClassLoader 系統類加載器
4.2類加載器的作用
1、Bootstrap ClassLoader 根類加載器
也被稱為引導類加載器,負責Java核心類的加載,比如System,String等。在JDK中JRE的lib目錄下rt.jar文件中。
2、Extension ClassLoader 擴展類加載器
負責JRE的擴展目錄中jar包的加載。在JDK中JRE的lib目錄下ext目錄
3、Sysetm ClassLoader 系統類加載器
負責在JVM啟動時加載來自java命令的class文件,以及classpath環境變量所指定的jar包和類路徑。通過這些描述我們就可以知道我們常用的東西的加載都是由誰來完成的。到目前為止我們已經知道把class文件加載到內存了,那么,如果我們僅僅站在這些class文件的角度,我們如何來使用這些class文件中的內容呢?這就是我們反射要研究的內容。
5. JVM眼中的相同的類
在JVM中,不可能存在一個類被加載兩次的事情!一個類如果已經被加載了,當再次試圖加載這個類時,類加載器會先去查找這個類是否已經被加載過了,如果已經被加載過了,就不會再去加載了。
但是,如果一個類使用不同的類加載器去加載是可以出現多次加載的情況的!也就是說,在JVM眼中,相同的類需要有相同的class文件,以及相同的類加載器。當一個class文件,被不同的類加載器加載了,JVM會認識這是兩個不同的類,這會在JVM中出現兩個相同的Class對象!甚至會出現類型轉換異常!
6. 類加載器的委托機制
首先委托類加載器的父類去加載,如果父類無法加載則自己加載
當系統類加載器去加載一個類時,它首先會讓上級去加載,即讓擴展類加載器去加載類,擴展類加載器也會讓它的上級引導類加載器去加載類。如果上級沒有加載成功,那么再由自己去加載!
例如我們自己寫的Person類,一定是存放到CLASSPATH中,那么一定是由系統類加載器來加載。當系統類加載器來加載類時,它首先把加載的任務交給擴展類加載去,如果擴展類加載器加載成功了,那么系統類加載器就不會再去加載。這就是代理模式了!
相同的道理,擴展類加載器也會把加載類的任務交給它的“上級”,即引導類加載器,引導類加載器加載成功,那么擴展類加載器也就不會再去加載了。引導類加載器是用C語言寫的,是JVM的一部分,它是最上層的類加載器了,所以它就沒有“上級了”。它只負責去加載“內部人”,即JDK中的類,但我們知道Person類不是我們自己寫的類,所以它加載失敗。
當擴展類加載器發現“上級”不能加載類,它就開始加載工作了,它加載的是lib\ext目錄下的jar文件,當然,它也會加載失敗,所以最終還是由系統類加載器在CLASSPATH中去加載Person,最終由系統類加載器加載到了Person類。
代理模式保證了JDK中的類一定是由引導類加載加載的!這就不會出現多個版本的類,這也是代理模式的好處。
6.1 類加載器之間的父子關系和管轄范圍圖
7. 自定義類加載器
我們也可以通過繼承ClassLoader類來完成自定義類加載器,自定義類加載器的目的一般是為了加載網絡上的類,因為這會讓class在網絡中傳輸,為了安全,那么class一定是需要加密的,所以需要自定義的類加載器來加載(自定義的類加載器需要做解密工作)。
ClassLoader加載類都是通過loadClass()方法來完成的,loadClass()方法的工作流程如下:
調用findLoadedClass()方法查看該類是否已經被加載過了,如果該沒有加載過,那么這個方法返回null
判斷findLoadedClass()方法返回的是否為null,如果不是null那么直接返回,這可以避免同一個類被加載兩次
如果findLoadedClass()返回的是null,那么就啟動代理模式(委托機制),即調用上級的loadClass()方法,獲取上級的方法是getParent(),當然上級可能還有上級,這個動作就一直向上走
如果getParent().loadClass()返回的不是null,這說明上級加載成功了,那么就加載結果
如果上級返回的是null,這說明需要自己出手了,這時loadClass()方法會調用本類的findClass()方法來加載類
這說明我們只需要重寫ClassLoader的findClass()方法,這就可以了!如果重寫了loadClass()方法覆蓋了代理模式!
OK,通過上面的分析,我們知道要自定義一個類加載器,只需要繼承ClassLoader類,然后重寫它的findClass()方法即可。那么在findClass()方法中我們要完成哪些工作呢?
- 找到class文件,把它加載到一個byte[]中;
- 調用defineClass()方法,把byte[]傳遞給這個方法即可。
loadClass()方法的實現代碼
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {Class<?> clazz = findLoadedClass(className);if (clazz == null) {ClassNotFoundException suppressed = null;try {clazz = parent.loadClass(className, false);} catch (ClassNotFoundException e) {suppressed = e;}if (clazz == null) {try {clazz = findClass(className);} catch (ClassNotFoundException e) {e.addSuppressed(suppressed);throw e;}}}return clazz; }自定義類加載器FileSystemClassLoader
public class FileSystemClassLoader extends ClassLoader {private String classpath;public FileSystemClassLoader() {}public FileSystemClassLoader(String classpath) {this.classpath = classpath;}@Overridepublic Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] datas = getClassData(name);if(datas == null) {throw new ClassNotFoundException("類沒有找到:" + name);}return this.defineClass(name, datas, 0, datas.length);} catch (IOException e) {e.printStackTrace();throw new ClassNotFoundException("類找不到:" + name);}}private byte[] getClassData(String name) throws IOException {name = name.replace(".", "\\") + ".class";File classFile = new File(classpath, name);return FileUtils.readFileToByteArray(classFile);} } ClassLoader loader = new FileSystemClassLoader("F:\\classpath"); Class clazz = loader.loadClass("cn.itcast.utils.CommonUtils"); Method method = clazz.getMethod("md5", String.class); String result = (String) method.invoke(null, "qdmmy6"); System.out.println(result);8. ClassLoader
| getParent() | 獲取上級類加載器 |
| loadClass() | 實現了類加載的加載流程,也就是算法框架 |
| findLoadedClass() | 查看該類是否被加載過 |
| findClass() | 真正去加載類,自定義類加載器需要重寫的方法 |
| defineClass() | 把Class的字節數組byte[]轉成Class |
總結
以上是生活随笔為你收集整理的Java基础:类加载器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java基础:面向对象
- 下一篇: Java高并发编程:多个线程之间共享数据