1.什麼是類加載機制java
虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終造成能夠被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。程序員
2.類的加載時機安全
圖中的七個階段表明這類的生命週期,其中加載、驗證、準備、初始化和卸載的順序是肯定的,按這種順序循序漸進的「開始」,而解析則不必定:它在某些狀況下能夠在初始化階段以後再開始,這是爲了支持Java語言的運行時綁定。另外要注意:剛纔的5個階段一般都是相互交叉地混合式進行,一般會在一個階段執行的過程當中調用、激活另一個階段。數據結構
加載階段什麼時候開始沒有作強制約束,但對於初始化階段,虛擬機規範則規定了有且只有5種狀況必須對類進行「初始化」: 函數
■ 遇到new、getstatic、putstatic或invokestatic這4條字節碼指令時。觸發場景:new關鍵字實例化對象、讀取或設置一個類靜態字段(被final修飾、已在編譯器放入常量池的除外)、調用另外一個類的靜態方法。this
■ 使用java.lang.reflect包的方法對類進行反射調用時。spa
■ 初始化一個類時,若是發現其父類還沒初始化,就要先初始化父類。指針
■ JVM啓動時,用戶指定一個可執行的主類,先初始化這個主類。code
■ 當使用JDK 1.7動態語言支持時,若是一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,切方法句柄所對應的類沒有初始化時。對象
3.類的加載過程
加載
加載階段JVM完成三件事:
■ 經過類的全限定名來獲取定義此類的二進制數據流。(若是沒有找到對應類文件,只有在類實際使用時才拋出錯誤)
■ 將數據流分析並轉化爲方法區(JVM對運行數據區的劃分結構,包括堆、棧、方法區等)的運行時數據結構。另外,這裏處理了部分檢驗,如對類文件的魔數的驗證、檢查文件是否過長或太短、肯定是否有父類。
■ 在內存中生成一個表明這個類的java.lang.class對象,做爲方法區這個類的各類數據的訪問入口。
鏈接
鏈接指的是將Java類的二進制代碼合併到JVM的運行狀態之中的過程,鏈接以前必須被成功加載。鏈接分三個步驟:
■ 驗證:用來確保Java類的二進制表示在結構是是徹底正確的、符合虛擬機規範的。如文件格式驗證(是否以魔數0xCAFEBABE開頭、主次版本號是都被虛擬機支持、常量類型是否都被支持)、元數據驗證(就是語義分析,如是否有父類、這個類的父類是否繼承了不被容許的類等)、字節碼驗證(經過數據流和控制流分析,肯定程序語義是合法的)、符號引用驗證(將符號引用轉化爲直接引用,發生在解析階段),若是驗證出現錯誤的話,會拋出java.lang.verifyError錯誤異常。
■ 準備:正式爲類變量(被static修飾)分配內存並設置類變量初始值(數據類型的零值)的階段,這些變量所使用的內存都將在方法區(持久代)中進行分配。
■ 解析:將常量池內的符號引用替換爲直接引用的過程,目的是確保這些被引用的類能被正確的找到。符號引用:以一組符號來描述引用的目標,符號能夠是任意無歧義的字面量。直接引用:能夠是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。
初始化
類的初始化是延遲的,直到第一次被主動使用(active use),JVM纔會初始化類。初始化過程的主要操做是執行靜態代碼塊和初始化靜態域,在一個類被初始化以前,它的直接父類也要被初始化,可是一個接口的初始化不會引發其父接口的初始化。
4.類加載的推進者--類加載器
Java中的全部類,必須被加載到JVM中才能運行,這個加載的工做是由JVM中的類加載器完成,類加載器的實質是把類文件從硬盤讀取到內存中,JVM加載類時都是經過ClassLoader的loadClass()方法,此方法使用了雙親委派模式。
接下來咱們看loadClass方法中雙親委派的實現:
圖中有一個同步代碼塊synchronized (getClassLoadingLock(name)),咱們來看getClassLoadingLock(name)的做用是什麼:
咱們這裏看到parallelLockMap變量,根據這個變量進行不一樣的操做,若是變量是null,直接返回this;若是不爲null,就新建一個對象,調用putIfAbsent方法給剛建好的對象賦值,那這個變量的來源是哪?
咱們發現這個變量是ClassLoader類的成員變量,這個變量的初始化工做在ClassLoader的構造函數中:
這裏咱們能夠看到構造函數根據一個屬性ParallelLoaders
的Registered
狀態的不一樣來給parallelLockMap
賦值。 那ParallelLoaders又來自何方呢?
咱們發現,在ClassLoader類中包含一個靜態內部類private static class ParallelLoaders
,在ClassLoader
被加載的時候這個靜態內部類就被初始化。這個類的意思就是:封裝了並行的可裝載的類型的集合。
綜上源碼,getClassLoadingLock(String className)的做用:爲類的加載操做返回一個鎖對象。爲了向後兼容,若是當前的classloader對象註冊了並行能力,方法返回一個與指定的名字className相關聯的特定對象,不然,直接返回當前的classloader對象。咱們看到,classloader源碼中還有一個resolveClass方法,它的做用是:連接指定的類,這個方法給classloader用來連接一個類,若是這個類已經被連接過了,那麼這個方法只作一個簡單的返回;不然,這個類將被按照Java™規範中的Execution描述進行連接。
5.類加載器總結
java中的類大體分三種:系統類、擴展類、程序員自定義的類。
類加載方式有兩種:隱式加載,程序在運行過程當中碰到new關鍵字等方式生成對象時,隱式調用類加載器;顯式加載,經過Class.forName()等方法,顯式加載類。
類加載的動態性體現:當運行一個由n多類組成的應用程序時,它會先把保證程序運行的基礎類一次性加載入JVM中,其餘類當用到時再進行加載,這樣節省了內存開銷。
Java類加載器:加載器的實質也是類,功能是把類加載入JVM,但JVM的加載器有三個,一方面是是各自負責各自的區塊,另外一方面實現委託模型。其層次結構:
類加載器之間協調工做:那當咱們須要加載一個類時,是由哪個加載器來加載的呢?在這裏Java採用委託模型機制,這個機制就是「類加載器有載入類的需求時,會先請示其Parent使用其搜索路徑幫忙載入,若是Parent找不到,再依照本身的搜索路徑搜索類」。好比咱們本地有個Test類,調用Test.class.getClassLoader()時得到的加載器爲AppClassLoader,AppClassLoader的父加載器爲ExtClassLoader,ExtClassLoader的父加載器爲根加載器,但在IDE控制檯打印時爲null,是由於它由C++編寫,在Java中看不到它。默認狀況下,採用AppClassLoader加載程序類。
採用「委託機制」是從安全考慮的。試想,若是自定義了一個"java.lang.string"的惡意類,且其被加載入JVM中,會引發嚴重的後果。而採用「委託機制」,這個類永遠只會由跟加載器加載。