本文這三章的筆記整理。java
類加載的過程能夠簡單分爲三個階段:sql
JVM
規範規定了每一個類或接口在首次主動使用的時候都須要進行初始化,規定了如下六種主動使用類的場景:數據庫
new
關鍵字會致使類的初始化main()
的類)也會初始化除了以上六種狀況外,其他的都叫被動使用,不會致使類的加載和初始化,好比引用類的靜態常量不會致使類的初始化。編程
前面也說了類加載能夠簡單分爲三個階段:安全
下面先來看一下加載階段。網絡
加載階段就是將class
文件中的二進制數據讀取到內存之中,而後將該字節流表明的靜態存儲結構轉換爲方法區中運行時數據結構,而且在堆中生成一個該類的java.lang.Class
對象,做爲訪問方法區數據結構的入口。數據結構
類加載的最終產物就是堆內存中的class
對象,JVM
規範中指出類加載是經過一個全限定名去獲取二進制數據流,來源包括:多線程
class
文件:這是最多見的格式,就是加載javac
編譯後的字節碼文件ASM
能夠動態生成,或者能夠經過動態代理java.lang.Proxy
生成等RMI
JAR
、WAR
包MySQL
中的BLOB
字段類型的數據class
文件而且動態加載:好比Thrift
、Avro
等序列化框架,將某個schema
生成若干個class
文件並進行加載類加載階段結束後,JVM
會將這些二進制字節流按照JVM
定義的格式存放在方法區中,造成特定的數據結構後再在堆內存中實例化一個java.lang.Class
對象。架構
該階段能夠分爲三個小階段:併發
須要注意的是這三個小階段其實不是順序進行的,而是交叉着進行的,也就是解析的時候其實也會有驗證的過程。
驗證是爲了確保字節流所包含的內容符合JVM
規範,而且不會出現危害JVM
自身安全的代碼,當字節流信息不符合要求的時候,會拋出VerifyError
這樣的異常或其子異常,驗證的信息包括:
包括:
0xCAFEBABE
)元數據驗證實際上是進行語義分析的過程,語義分析是爲了確保字節流符合JVM
規範要求,包括:
final
的類字節碼驗證主要是驗證程序的控制流程,包括:
驗證符號引用轉換爲直接引用的合法性,保證解析動做的順利執行,包括:
通過驗證後,就開始了準備階段,這階段比較簡單,就是對對象的靜態變量分配內存而且設置初始值,類變量的內存會被分配到方法區中。設置初始值就是爲相應的類變量給定一個相關類型在沒有被設置時的默認值,好比Int
的初始值爲0,引用的初始值爲null
。
解析就是在常量池中尋找類、字段、接口和方法的符號引用,而且將這些符號引用替換成直接引用的過程。解析主要針對類接口、字段、類方法和接口方法進行的,包括:
初始化階段主要就是執行<clinit>
方法的過程,該方法是編譯階段生成的,也就是說包含在字節碼文件中,該方法包含了全部類變量的賦值動做和靜態語句塊的執行代碼。另外一方面,<clinit>
與構造方法不一樣,不須要顯式調用父類構造器,虛擬機會保證父類的<clinit>
方法最早執行。
還須要注意的是<clinit>
只能被虛擬機執行,虛擬機還會保證多線程下的安全性,所以,若是在靜態代碼塊中若是包含了加載其餘類的操做可能會引發死鎖,例子能夠看這裏。
JVM
中的三類核心類加載器JVM
中有三類核心類加載器,分別是:
C++
編寫,負責JVM
核心類庫的加載,好比加載整個java.lang
包中的類jre/lib/ext
子目錄下的類庫,純Java
實現,是URLClassLoader
的子類classpath
下的類庫,應用類加載器的父加載器爲擴展類加載器,同時它也是自定義類加載器的默認父加載器一個類加載器加載一個類的時候,並不會嘗試直接加載該類,而是先交給父加載器嘗試加載,一直到頂層的父加載器(啓動類加載器),若是父加載器加載失敗,則會本身嘗試加載,圖示以下:
JDK
中提供了不少SPI
(Service Provider Interface
),好比JDBC
等,JDBC
只規定了這些接口之間的邏輯關係,但不提供具體的實現,換句話說,JDBC
徹底透明瞭應用程序和第三方廠商數據庫驅動的具體實現,應用程序只須要面向接口編程便可。但問題是:
java.lang.sql
中的全部接口都是由JDK
提供的,加載這些接口的類加載器是啓動類加載器因爲雙親委派機制,Connections
、Statement
等都是由啓動類加載器加載,而第三方JDBC
驅動包中的實現不會被加載。解決這個問題的關鍵,就是使用了線程上下文類加載器打破了雙親委派機制。
好比MySQL
驅動的加載過程,就是經過線程上下文類加載器加載的,
private static Connection getConnection(String url, Properties info, Class<?> caller) throws SQLException { //... if (callerCL == null || callerCL == ClassLoader.getPlatformClassLoader()) { callerCL = Thread.currentThread().getContextClassLoader(); } while(true) { //... if (isDriverAllowed(aDriver.driver, callerCL)) { } } //... } private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) { //... try { aClass = Class.forName(driver.getClass().getName(), true, classLoader); } catch (Exception var5) { result = false; } //... return result; }
經過線程上下文類加載器,就變成了啓動類加載器去委託子類加載器去加載實現的方式,也就是JDK
本身親自打破了雙親委派機制這種方式,這種加載方式幾乎涉及全部的SPI
加載,包括JAXB
、JCE
、JBI
等。