JAVA爲咱們提供了兩種動態加載機制。
第一種是隱式機制。其實new一個對象和調用類的靜態方法時,就是隱式機制在工做。
第二種是顯示機制。顯示的機制又有兩種策略
第一種是用public static Class<?> forName(String className)。public static Class<?> forName(String name, boolean initialize, ClassLoader loader),第二種是用java.lang.ClassLoader的loadClass())。 java
Java程序啓動時,並非一次把全部的類所有加載後再運行,它老是先把保證程序運行的基礎類一次性加載到jvm中,其它類等到jvm用到的時候再加載。
其中類加載過程:
一、尋找 jre 目錄,尋找jvm.dll,並初始化JVM;
二、產生一個Bootstrap Loader(啓動類加載器,用C++實現),在java虛擬機啓動的時候會利用這個類加載器來加載 JDK安裝目錄下的 /JRE/LIB/rt.jar等文件。 經過System.getProperty("sun.boot.class.path")能夠查詢。
三、Bootstrap Loader自動加載Extended Loader(標準擴展類加載器 ,用java實現),並將其父Loader設爲Bootstrap Loader。這個ClassLoader是用來加載java的擴展API的,加載JDK安裝目錄下的/JRE/LIB/ext目錄中的類。能夠經過System.getProperty("java.ext.dirs")進行查詢。
也能夠指定搜索路徑: java -Djava.ext.dirs=d:/projects/testproj/classes HelloWorld
四、Bootstrap Loader自動加載AppClass Loader(系統類加載器 用java實現),並將其父Loader設爲Extended Loader。這個ClassLoader是用來加載用戶機器上CLASSPATH設置目錄中的Class的。經過System.getProperty("java.class.path")能夠查詢。也能夠覆蓋環境變量: java -cp ./lavasoft/classes HelloWorld
五、最後由AppClass Loader加載HelloWorld類。
以上就是類加載的最通常的過程。
ExtClassLoader和AppClassLoader在JVM啓動後,會在JVM中保存一份,而且在程序運行中沒法改變其搜索路徑。若是想在運行時從其餘搜索路徑加載類,就要產生新的類加載器。 web
在JAVA中,一個類用其徹底匹配類名(fully qualified class name)做爲標識,這裏指的徹底匹配類名是包名和類名。不過在JVM中一個類是用其全名再附加上一個加載類ClassLoader的實例做爲惟一標識。 apache
同一個ClassLoader加載的類文件,只有一個Class實例。
可是,若是同一個類文件被不一樣的ClassLoader載入,則會有兩份不一樣的ClassLoader實例(前提是着兩個類加載器不能用相同的父類加載器)
雙親委託模式:在任何一個自定義ClassLoader加載一個類以前,它都會先委託它的父親ClassLoader進行加載,只有當父親ClassLoader沒法加載成功後,纔會由本身加載。
《特例是線程上下文類加載器,使用線程上下文類加載器, 能夠在執行線程中, 拋棄雙親委派加載鏈模式, 使用線程上下文裏的類加載器加載類.
典型的例子有, 經過線程上下文來加載第三方庫jndi實現, 而不依賴於雙親委派. 大部分java app服務器(jboss, tomcat..)也是採用contextClassLoader來處理web服務。
以 Apache Tomcat 來講,每一個 Web 應用都有一個對應的類加載器實例。該類加載器也使用代理模式,所不一樣的是它是首先嚐試去加載某個類,若是找不到再代理給父類加載器。這與通常類加載器的順 序是相反的。這是 Java Servlet 規範中的推薦作法,其目的是使得 Web 應用本身的類的優先級高於 Web 容器提供的類。
這種代理模式的一個例外是:Java 核心庫的類是不在查找範圍以內的。這也是爲了保證 Java 核心庫的類型安全。 》 tomcat
在JVM加載類的時候,須要通過三個步驟,裝載、鏈接、初始化。裝載就是找到相應的class文件,讀入JVM,初始化就不用說了,最主要就說說鏈接。
forName加載的時候就會將Class進行解釋和初始化。forName也有另一個版本的方法,能夠設置是否初始化以及設置ClassLoader,在此就很少講了。 loadClass加載類實際上就是加載的時候並不對該類進行解釋,所以也不會初始化該類。
安全
加載過程當中會先檢查類是否被已加載,檢查順序是自底向上,而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。
卸載重載:一個已經加載的類是沒法被更新的,若是你試圖用同一個ClassLoader再次加載同一個類,就會獲得異常(java.lang.LinkageError: duplicate classdefinition),咱們只可以從新建立一個新的ClassLoader實例來再次加載新類。至於原來已經加載的類,開發人員沒必要去管它,由於它可能還有實例正在被使用,只要相關的實例都被內存回收了,那麼JVM就會在適當的時候把不會再使用的類卸載。 服務器
真正完成類的加載工做是經過調用 defineClass來實現的;而啓動類的加載過程是經過調用 loadClass來實現的。前者稱爲一個類的定義加載器(defining loader),後者稱爲初始加載器(initiating loader)。在 Java 虛擬機判斷兩個類是否相同的時候,使用的是類的定義加載器。 app
《注意事項:若是A是由Bootstrap Loader所載入,這個時候,要加入B,先交給parent進行查詢,這時parent爲null,交給本身查詢,本身又沒有,就報錯。》 jvm
線程上下文類加載器 ide
線程上下文類加載器(context class loader)是從 JDK 1.2 開始引入的。類 java.lang.Thread 中的方法getContextClassLoader() 和 setContextClassLoader(ClassLoader cl) 用來獲取和設置線程的上下文類加載器。若是沒有經過 setContextClassLoader(ClassLoader cl) 方法進行設置的話,線程將繼承其父線程的上下文類加載器。Java 應用運行的初始線程的上下文類加載器是系統類加載器。在線程中運行的代碼能夠經過此類加載器來加載類和資源。 ui
前面提到的類加載器的代理模式並不能解決 Java 應用開發中會遇到的類加載器的所有問題。Java 提供了不少服務提供者接口(Service Provider Interface,SPI),容許第三方爲這些接口提供實現。常見的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。這些 SPI 的接口由 Java 核心庫來提供,如 JAXP 的 SPI 接口定義包含在javax.xml.parsers 包中。這些 SPI 的實現代碼極可能是做爲 Java 應用所依賴的 jar 包被包含進來,能夠經過類路徑(CLASSPATH)來找到,如實現了 JAXP SPI 的 Apache Xerces 所包含的 jar 包。SPI 接口中的代碼常常須要加載具體的實現類。如 JAXP 中的 javax.xml.parsers.DocumentBuilderFactory 類中的newInstance() 方法用來生成一個新的 DocumentBuilderFactory 的實例。這裏的實例的真正的類是繼承自javax.xml.parsers.DocumentBuilderFactory , 由 SPI 的實現所提供的。如在 Apache Xerces 中,實現的類是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl 。 而問題在於,SPI 的接口是 Java 核心庫的一部分,是由引導類加載器來加載的;SPI 實現的 Java 類通常是由系統類加載器來加載的。引導類加載器是沒法找到 SPI 的實現類的,由於它只加載 Java 的核心庫。它也不能代理給系統類加載器,由於它是系統類加載器的祖先類加載器。也就是說,類加載器的代理模式沒法解決這個問題。
線程上下文類加載器正好解決了這個問題。若是不作任何的設置,Java 應用的線程的上下文類加載器默認就是系統上下文類加載器。在 SPI 接口的代碼中使用線程上下文類加載器,就能夠成功的加載到 SPI 實現的類。線程上下文類加載器在不少 SPI 的實現中都會用到.