Java語言中,類只有被加載到JVM中才能運行,當運行指定的java程序時,JVM會將編譯生成的 .class文件按照一定的規則加載到內存中,並組織成爲一個完整的應用程序。類的加載過程是由類加載器完成的(即由ClassLoader和它的子類完成),而類加載器本身也是一個類,其實質是將類文件由硬盤加載到內存中。
類的加載方式有兩種:
(1)顯式加載
通過調用class.forName()方法將所需的類加載到JVM中
(2)隱式加載
程序在創建新的對象時,隱式地調用類加載器把對應的類加載到JVM中
在java語言中,類的加載是動態且靈活的,往往一個大的項目包含很多類,而每一個類或接口都對應一個.class文件,當程序運行時只需要將需要的類(保證程序運行的基礎類,例如基類)加載到JVM中,暫時不需要的類可以先不加載,這樣一方面可以提高運行速度,另一方面也可以降低程序運行時對內存的開銷。而且,每一個類文件都可以看成是動態的加載單元,當項目需要對某個類進行修改時,修改完畢後只需要重新編譯加載被修改的類即可,而不用全部的類都重新進行編譯。
類可以分爲三種:系統類、擴展類、自定義類,而java根據不同的類提供了不同的類加載器
Bootstrap Loader <==加載系統類(jre/lib/rt.jar的類)
↘
ExtClassLoader <==加載擴展類(jar/lib/etc/*.jar的類)
↘
AppClassLoader <==加載應用類(classpath指定的目錄或jar中的類)
具體步驟:
(1)首先java.exe會找到JRE,並且找到位於JRE內部的jvm.dll,這纔是真正的java虛擬機,然後加載到動態庫,激活java虛擬機。
(2)進行初始化操作,結束之後產生Bootstrap Loader啓動類加載器
(3)Bootstrap Loader除了進行一些基本的初始化動作外,最重要的是加載ExtClassLoader擴展類加載器,並且設定其Parent爲null,也就代表其父加載器爲Bootstrap Loader
(4)然後Bootstrap Loader再要求加載Launcher.java中的AppClassLoader(自定義類加載器),並設定其Parent爲ExtClassLoader實體,這兩個加載器都是以靜態類的形式存在的。
※需要注意的是,其實parent是誰跟被誰加載的並沒有直接關係
我們可以測試一下:
package test; public class classloader { public static void main(String[] args) throws Exception{ ClassLoader App = classloader.class.getClassLoader();//class加載器 System.out.println(App); ClassLoader Ext = App.getParent();//上一層加載器 System.out.println(Ext); ClassLoader Boot = Ext.getParent();//根部加載器 System.out.println(Boot); } }
運行結果:
sun.misc.Launcher$AppClassLoader@39579371
sun.misc.Launcher$ExtClassLoader@2490fd20
null
Bootstrap Loader輸出null的原因是它是由C++語言實現的,所以在java語言中看不到
程序說明classloader這個類是由AppClassLoader加載的
類的加載主要有三步:
(1)裝載:根據查找路徑找到相應的class文件並導入
(2)鏈接:檢查class文件是否正確-->給類中的靜態變量分配存儲空間-->將符號引用轉換成直接引用
(3)初始化:靜態變量和靜態代碼塊的初始化操作
雙親委託機制:
雙親委託模式也就是一個類加載器請求另一個類加載器來加載類型的過程。
除啓動類加載器以外的每一個類加載器,都有一個“雙親”類加載器
,在某個特定的類加載器試圖以常用方式加載某個類以前,它會先默認地將這個任務“委派”給它的雙親,請求它的雙親來加載這個類。這個雙親再依次請求它自己的雙親來加載這個類型。這個委派的過程一直向上繼續,直到達到啓動類加載器,通常啓動類加載器是委派鏈中的最後一個類加載器。如果一個類加載器的雙親類加載器有能力來加載這個類型。則這個類加載器返回這個類型。否則,這個類加載器試圖自己來加載這個類。
當一個程序運行時,虛擬機在啓動時實例化了兩個用戶自定義類加載器:一個“擴展類加載器”,一個“自定義類加載器”.這些類裝載器和啓動類加載器一起聯入一個Parent-Child委託鏈中,啓動類加載器在最頂端。
例如:
package java.lang public class String //定義一個和JDK中String一樣的類 { public static void main(String[] args) { } }
我們定義了一個與JDK中String一模一樣的類,包括java.lang都是一樣的,只是在我們定義的String類中包含了主函數。
運行結果:
java.lang.NoSuchMethodError: main
Exception in thread "main"
出現這樣的結果的原因是:
運行代碼時,JVM會首先創建一個自定義類加載器,不妨叫做AppClassLoader,並把這個加載器鏈接到委託鏈中:AppClassLoader -> ExtClassLoader -> BootstrapLoader。然後AppClassLoader會將加載java.lang.String的請求委託給ExtClassLoader,而 ExtClassLoader又會委託給最後的啓動類加載器BootstrapLoader。啓動類加載器BootstrapLoader只能加載JAVA_HOME\jre\lib中的class類(即J2SE API),而標準API中確實有一個java.lang.String,但是這個類並不是我們自己定義的類。而BootstrapLoader以爲找到了這個類,便加載了j2se api中的java.lang.String。
最後出現上面的加載錯誤,注意這並不是異常,JVM已經退出了,因爲API中的String類是沒有main方法的。