Java中的所有類,都需要由類加載器裝載到JVM中才能運行。類加載器本身也是一個類,而它的工作就是把class文件從硬盤讀取到內存中。在寫程序的時候,我們幾乎不需要關心類的加載,因為這些都是隱式裝載的,除非我們有特殊的用法,像是反射,就需要顯式的加載所需要的類。
類裝載方式,有兩種 :
1.隱式裝載, 程序在運行過程中當碰到通過new 等方式生成對象時,隱式調用類裝載器加載對應的類到jvm中,
2.顯式裝載, 通過class.forname()等方法,顯式加載需要的類
Java類的加載是動態的,它並不會一次性將所有類全部加載后再運行,而是保證程序運行的基礎類(像是基類)完全加載到jvm中,至於其他類,則在需要的時候才加載。這當然就是為了節省內存開銷。
Java的類加載器有三個,對應Java的三種類:
Bootstrap Loader :啟動類加載器,是虛擬機自身的一部分。負責將存放在\lib目錄中的類庫加載到虛擬機中。其無法被Java程序直接引用。 負責加載系統類 (指的是內置類,像是String,對應於C#中的System類和C/C++標准庫中的類)
ExtClassLoader : 負責加載擴展類(就是繼承類和實現類)
AppClassLoader :負責加載用戶類路徑(ClassPath)上所指定的類庫(程序員自定義的類)
JVM中類的加載是由類加載器(ClassLoader)和它的子類來實現的,Java中的類加載器是一個重要的Java運行時系統組件,它負責在運行時查找和裝入類文件中的類。
由於Java的跨平台性,經過編譯的Java源程序並不是一個可執行程序,而是一個或多個類文件。當Java程序需要使用某個類時,JVM會確保這個類已經被加載、連接(驗證、准備和解析)和初始化。
類的加載是指把類的.class文件中的數據讀入到內存中,通常是創建一個字節數組讀入.class文件,然后產生與所加載類對應的Class對象。加載完成后,Class對象還不完整,所以此時的類還不可用。
當類被加載后就進入連接階段,這一階段包括
驗證:為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。
准備:為靜態變量分配內存並設置默認的初始值。
解析:將符號引用替換為直接引用。
最后JVM對類進行初始化,包括:1)如果類存在直接的父類並且這個類還沒有被初始化,那么就先初始化父類;2)如果類中存在初始化語句,就依次執行這些初始化語句。
類的加載是由類加載器完成的,類加載器包括:啟動類加載器(BootStrap)、擴展類加載器(Extension)、應用程序類加載器(Application)。
從Java 2(JDK 1.2)開始,類加載過程采取了雙親委派模型(PDM)。PDM更好的保證了Java平台的安全性,在該機制中,JVM自帶的Bootstrap是啟動類加載器,其他的加載器都有且僅有一個父類加載器。類的加載首先請求父類加載器加載,父類加載器無能為力時才由其子類加載器自行加載。JVM不會向Java程序提供對Bootstrap的引用。
雙親委派模型:要求除了頂層的啟動類加載器外,其余加載器都應當有自己的父類加載器。類加載器之間的父子關系,一般不會以繼承的關系來實現,而是通過組合關系復用父加載器的代碼。
工作過程:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器完成。
每個層次的類加載器都是如此,因此所有的加載請求最終都應該傳送到頂層的啟動類加載器中,
只有到父加載器反饋自己無法完成這個加載請求(它的搜索范圍沒有找到所需的類)時,子加載器才會嘗試自己去加載。
為什么要使用:Java類隨着它的類加載器一起具備了一種帶優先級的層次關系。
比如java.lang.Object,它存放在rt.jar中,無論哪個類加載器要加載這個類,最終都是委派給啟動類加載器進行加載,
因此Object類在程序的各個類加載器環境中,都是同一個類。
自己編寫一個與rt.jar類庫中已有類重名的java類,可以正常編譯,但無法被加載運行。
委托機制的意義 — 防止內存中出現多份同樣的字節碼
比如兩個類A和類B都要加載System類:
如果不用委托而是自己加載自己的,那么類A就會加載一份System字節碼,然后類B又會加載一份System字節碼,這樣內存中就出現了兩份System字節碼。
如果使用委托機制,會遞歸的向父類查找,也就是首選用Bootstrap嘗試加載,如果找不到再向下。這里的System就能在Bootstrap中找到然后加載,如果此時類B也要加載System,也從Bootstrap開始,此時Bootstrap發現已經加載過了System那么直接返回內存中的System即可而不需要重新加載,這樣內存中就只有一份System的字節碼了。
能不能自己寫個類叫java.lang.System?
答案:通常不可以,但可以采取另類方法達到這個需求。
解釋:為了不讓我們寫System類,類加載采用委托機制,這樣可以保證父類加載器優先,父類加載器能找到的類,子加載器就沒有機會加載。而System類是Bootstrap加載器加載的,就算自己重寫,也總是使用Java系統提供的System,自己寫的System類根本沒有機會得到加載。
但是,我們可以自己定義一個類加載器來達到這個目的,為了避免雙親委托機制,這個類加載器也必須是特殊的。由於系統自帶的三個類加載器都加載特定目錄下的類,如果我們自己的類加載器放在一個特殊的目錄,那么系統的加載器就無法加載,也就是最終還是由我們自己的加載器加載。