JVM中ClassLoader類加載器的深入理解

JVM的體系結構圖

先來看一下JVM的體系結構,如下圖:

img

JVM的位置

JVM的位置,如下圖:

img

JVM是運行在操作系統之上的,與硬件沒有直接的交互,但是可以調用底層的硬件,用JIN(Java本地接口調用底層硬件)

JVM結構圖中的class files文件

class files文件,是保存在我們電腦本地的字節碼文件,.java文件經過編譯之後,就會生成一個.class文件,這個文件就是class files所對應的字節碼文件,如下圖:

JVM結構圖中的類加載器ClassLoader的解釋

 類加載器ClassLoader的作用

類加載器隻有一個作用,就是負責把我們本地的.class字節碼文件,加載到JVM中,以類模板的形式存在於JVM中。

類加載器加載的.class文件也是有要求的,並不是隻要是後綴名為.class的文件,都可以被類加載器加載,這個.class文件的必須是.java文件經過編譯後得到的字節碼文件,也就是指這個.class文件的開頭必須要是cafe babe字符單詞,它的內容是經過加密後的二進制文件,但是此二進制使用十六進制表示出來的,如下圖:

類加載器ClassLoader隻負責加載.class文件,但至於他是否可以運行,由JVM中的執行引擎Excution Engine決定的。

img

解釋:

Car.class是由.java文件編譯得來的.class文件,存在本地磁盤。

ClassLoader:類加載器,負責加載並初始化 類文件,得到真正的Class類,即模板。

Car Class:由Car.class字節碼文件,通過ClassLoader加載並且初始化得到,這個Car就是當前類的模板,這個Car Class模板就存在方法區。

car1,car2,car3:是由Car模板經過實例化而得,一個模板可以獲得多個實例化對象。

拿car1舉例,car1.getClass()可以得到模板Car類,Car.getClassLoader()可得到其裝載器。

類加載器的種類

一共有四類。

虛擬機自帶的加載器:

啟動類加載器,也叫根加載器(BootStrap)。由C++編寫,程序中自帶的類,存儲在 $JAVAHOME/jre/lib/rt.jar中,如object類等

擴展類加載器(Extension),Java編寫,在我們看到的類路徑中,凡是以Javax開頭的,都是拓展包,存儲在 $JAVAHOME/jre/lib/ext/*.jar 中,為什麼會有擴展包?是因為原本的java包中在功能上,不能滿足我們瞭,所以我們就在原本的java包的基礎上擴展出瞭一些新的功能,而形成的新的包就叫做擴展包。

應用程序類加載器(AppClassLoader),即平時程序中自定義的類 new出來的

用戶自定義的加載器:

Java.lang.ClassLoader的子類,用戶可以定制類的加載方式,即如果你的程序有特殊的需求,你也可以自定義你的類加載器的加載方式 ,進入ClassLoader的源碼,其為抽象類,因此在你定制化開發的時候,需要你定義自己的加載器類來繼承ClassLoader抽象類即可,即 MyClassLoader extends ClassLoader

java類的加載機制

首先你需要知道,當java程序需要加載一個類的時候,會去找類加載器,然後找類加載器裡面看是否有對應的類模板。

Java的類加載機制,永遠是以 根加載器->拓展類加載器->應用程序類加載器 這樣的一個順序進行加載的。什麼意思呢?就是如果能在BootStrapClassLoader類加載器的路徑下找到對應的類模板,那麼就不會再去擴展類加載器ExtensionClassLoader中找對應的類模板,如果能在擴展類加載器ExtensionClassLoader中找到對應的類模板,那麼就不會去應用程序類加載器AppClassLoader中找對應的類模板。類加載器的父子級關系,如下圖:

img

從上圖可以看出根加載器BootStrapClassLoader是擴展類加載器ExtensionClassLoader的父級,擴展類加載器ExtensionClassLoader是應用程序加載器AppClassLoader的父級,如下圖:

被某個類加載器加載的類,都會存放到這個類加載器的路徑中,當程序需要加載某個類的時候,會先去根加載器BootstrapClassLoader的路徑下,尋找是否有這個類的模板,如果沒有則會去這個類加載器的下一級,若有,則不會再去下一級尋找瞭。

利用obj.getClass().getClassLoader()方法,可以找到對應的類模板是存放在哪個類加載器的路徑下的,換一個說法,可以讓你知道在加載這個類模板所對應的類的字節碼文件的時候,是用的哪一個類加載器把這個類的字節碼文件加載成類模板的,如下圖:

那麼問題來瞭?為什麼說Object類是java自帶的類呢?java自帶的類到底可以在哪裡找到呢?java自帶的類可以 J A V A H O M E / j r e / l i b / r t . j a r 下 找 到 。 首 先 來 看 一 下 JAVAHOME/jre/lib/rt.jar下找到。首先來看一下 JAVAHOME/jre/lib/rt.jar下找到。首先來看一下JAVAHOME,也即是java的安裝目錄,如下圖:

然後咱來看一下$JAVAHOME/jre/lib/rt.jar中的包(rt其實也即是RunTime的縮寫),如下圖:

打開rt.jar壓縮包,它的目錄結構,如下圖:

Object類在java/lang包下,如下圖:

JVM中的類加載器,根加載器BootStrap,在一開始,就會把rt.jar壓縮包中的所有的 類的字節碼文件都加載到JVM中,變成類的模板,所以這些java自帶的類,對應的類的模板,都會被存放到根加載器BootStrap的路徑下面。故當java程序中需要用到java中的自帶類的時候,都會去根加載器BootStrap的路徑下去尋找類的模板。

雙親委派機制

官方概念:當一個類收到類加載請求後,他不會首先去加載這個類,而是把這個請求委派給父類去完成。每一個層次的類加載器都是如此,因此所有的類加載器請求都是應該傳到根加載器中的,隻有當其父類加載器自己無法完成這個請求的時候(在他的加載路徑下沒有找到所需加載的class),子類加載器才會嘗試自己去加載。

我的理解翻譯:當java程序需要加載一個類的時候,比如要加載java.lang.String類,會首先去根加載器類BootstrapClassLoader的路徑下去尋找,如果在根加載器路徑下可以找到java.lang.String類,那麼就不會再往下尋找java.lang.String類瞭,因為已經找到瞭;若在根加載器路徑下沒有找到java.lang.String類,則會去下一級擴展類加載器ExtensionClassLoader中尋找java.lang.String類,如果在擴展類加載器中找到瞭java.lang.String類,那麼就不會再去下一級類加載器中尋找瞭;如果沒有找到,則會去應用程序類加載器AppClassLoader中去尋找java.lang.String類,如果仍然沒有找到會報classNotFoundException異常。

采用雙親委派機制的好處:保證瞭我們寫的代碼不會污染到java的源代碼,因為隻要java的源代碼中存在我們即將要加載的類,那麼java程序在加載類的時候就會去頂層的根加載器BootstrapClassLoader路徑下去尋找類模板,然後把這個類加載。你就比如假設我們自己寫一個java.lang包,然後在這個包裡面寫一個String類,那麼如果java源代碼中沒有java.lang.String這個類,java程序在用到這個類加載的時候,會用到應用程序類加載器AppClassLoader路徑下的java.lang.String類,但是,我們都知道java源代碼中是有java.lang.String這個類的,也即是在根加載器BootstrapClassLoader路徑下有java.lang.String這個類,所以java程序在用到這個類加載它的時候,會加載根加載器BootstrapClassLoader路徑下的java.lang.String類,而不會去加載應用程序類加載器AppClassLoader下的java.lang.String類,這樣就保證瞭,我們在java程序的其它地方所用到的java.lang.String類的方法是正確的,怎麼個正確法呢?因為啊,假設你是加載的應用程序加載器AppClassLoader路徑裡的java.lang.String類也即是你自己寫的String類,那麼在java程序的其它地方如果使用到瞭源代碼java.lang.String類裡面的方法該怎麼辦?這樣是不是會出錯?這其實也就是,你寫的代碼污染瞭人傢寫的源代碼。

雙親委派機制的程序理解,如下圖:

沙箱安全機制

通過雙親委派機制,類的加載永遠都是從根加載器開始的,如果跟加載器中沒有對應的類模板,則會去下一級類加載器中尋找這個類模板,如果有的話則就不會去下一級尋找瞭。這樣就保證你所寫的代碼不會污染Java自帶的源代碼,保證瞭沙箱的安全。

為什麼說這樣不會污染java自帶的源代碼呢?因為隻要java的源代碼中存在我們即將要加載的類,那麼java程序在加載類的時候就會去頂層的根加載器BootstrapClassLoader路徑下去尋找類模板,然後把這個類加載。你就比如假設我們自己寫一個java.lang包,然後在這個包裡面寫一個String類,那麼如果java源代碼中沒有java.lang.String這個類,java程序在用到這個類加載的時候,會用到應用程序類加載器AppClassLoader路徑下的java.lang.String類,但是,我們都知道java源代碼中是有java.lang.String這個類的,也即是在根加載器BootstrapClassLoader路徑下有java.lang.String這個類,所以java程序在用到這個類加載它的時候,會加載根加載器BootstrapClassLoader路徑下的java.lang.String類,而不會去加載應用程序類加載器AppClassLoader下的java.lang.String類,這樣就保證瞭,我們在java程序的其它地方所用到的java.lang.String類的方法是正確的,怎麼個正確法呢?因為啊,假設你是加載的應用程序加載器AppClassLoader路徑裡的java.lang.String類也即是你自己寫的String類,那麼在java程序的其它地方如果使用到瞭源代碼java.lang.String類裡面的方法該怎麼辦?這樣是不是會出錯?這其實也就是,你寫的代碼污染瞭人傢寫的源代碼。

總結

到此這篇關於JVM中ClassLoader類加載器的文章就介紹到這瞭,更多相關JVM中ClassLoader類加載器內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: