我們都知道,我們得java程序得運行,實際是根據面向對象編程得原理,為一個個類創建對象,對象們協同工作,完成了程序得運行!
但是這些類,說到底是一個個得文件,二進制的class,如何變成為jvm所用的對象呢?,我們稱之為類加載!看classloader偶有心得,隨筆以記!
1 類加載原理(雙親委派機制)
說到雙親委派機制,那就祭出一張十分眼熟得圖!
這是一張很簡單易懂得圖!就是類加載是有父子關系得,當接受到類加載請求時,都是先去找自己得“爸爸”類加載去完成加載,找到則返回,找不到則跑錯!可參考下圖源碼理解:
上圖得這段代碼就是classload種得loadClass方法實現! 上圖可見,根據一個name加載類時,第一步會根據name得到一個鎖,保證類加載得並發安全!第二步就是判斷父類加載器是否為空,如果不為空,這調用父類加載得loadClass方法加載,如果為空,則第三步使用根加載器BootStrap根據名稱加載,如果以上步驟完成后,依然沒加載到類,在第四步調用findClass(name),實際是拋出一個 ClassNotFoundException(name)的異常!
類加載器主要有四種,根加載,擴展加載,應用加載,自定義加載,其父子順序主要可見 sun.misc.Lancher中代碼:
在Lancher的構造函數中,首先創建了ExtClassLoader 擴展加載,沒有傳遞parent加載器,則為根加載器BootStrap加載器為parent,而后又將ExtClassLoader 做為parent屬性,用以創建了AppClassLoader應用加載!
在Lancher中有一個根目錄屬性 bootClassPath,通過 System.getProperty("sun.boot.class.path"); 如果將其打印,可以發現是jdk下的lib下的一些jar包和classee目錄!在Lancher中的靜態內部類ExtClassLoader 實現了擴展加載,在調用getExtClassLoader時會調用createExtClassLoader方法給單例模式的ExtClassLoader instrance創建,在createExtClassLoader方法中可見調用了 getExtDirs()方法,實際是通過System.getProperty("java.ext.dirs")獲取擴展加載的類,打印可見該目錄是jre\lib\ext目錄。
在Lancher的靜態內部類 AppClassLoader中實現了應用加載,在調用 getAppClassLoader方法獲取應用加載器的代碼中可見調用了System.getProperty("java.class.path") 獲取了開發人員指定的resource路徑或默認路徑。
在這個完整的模式下,加載的類可以被它的類加載器緩存,沒必要重復加載,同時,如果定義了同一個名稱的兩個類,也只會加載一個,保證了安全。
通過閱讀ClassLoader的代碼可見,自定義類加載,實現ClassLoader即可,通過調用loadclass實現類加載! 如果想打破雙親委派加載模型,在自己重寫的loadClass方法中實現即可。
那么把一個class文件從jar包中讀取生成class對象,又可見以下圖:
加載: 通過類的全限定名獲取二進制字節流,把這個字節流代表的靜態存儲結構轉化為方法區的運行時數據接口,在內存中創建一個Class對象作為方法區內這個類的訪問入口。
驗證:1 文件格式驗證:
a class文件以0xCAFEBABE(咖啡baby)開頭
b 版本號是否在當前虛擬機可接受范圍
c 常量池中常量是否有不支持的類型
........
2 元數據驗證 : 主要是驗證語義,語法是否復合java語言規范,比如 是否有父類,父類是否是被final修飾過,如果不是抽象類,是否已完全實現抽象方法等
3 字節碼驗證 : 主要是驗證語義預發是否合乎邏輯,注意,是邏輯,不同2中的規范。比如long類型數據被以int加載到本地
4 符合引用驗證,驗證類中用到的引用是否自己有通過全限定名訪問的權限
准備:為類中定義的靜態變量分配內存
解析:解析內部的字段和方法,把符號引用替換為直接引用
初始化 :靜態變量獲取到了指定句柄,既指定值,執行靜態代碼。
以上為類加載的過程,那合適會出發類加載呢?有以下條件觸發:
1 遇到new getstatic putstatic invokestatic四個指令時,如果沒有初始化,則需要觸發初始化。這4個指令通常是new 實例化對象,讀取或設置static屬性,調用靜態方法是。
2 對一個類使用反射包下方法調用時
3 初始化一個類,但是發現起父類沒有初始化時
4 虛擬機啟動時,執行一個主類