前幾天本人正在愉快的寫代碼的時候忽然接到老大給的一個新任務,對支付相關的幾個類作代碼加密和安全性校驗工做,確保類來源的安全性。 java
那麼如今有了需求下一步就要來知足需求,這裏採用的方案是在加載過程當中進行類來源檢測和代碼解密的相關工做。接下來主要就是實現了一個類加載器。 程序員
通過一通操做終於實現好了這個加載器,通過測試也知足了類的相關解密和校驗工做,可謂是完美。然而,帥不過三秒,接下來運行的時候傻眼了,報了無數以前沒有的錯,發生在這個對象的equals()方法、isAssignableFrom()方法、isInstance()方法上。 數據庫
如:經過自定義加載器加載的對象使用instanceof關鍵字作對象所屬關係斷定時都爲false。 編程
最終經過查閱學習,從JVM的類加載機制上找到了解釋。安全
類加載器能夠說是Java語言的一項創新,也是構成Java平臺無關性的一塊基石。網絡
首先明確一下類加載器是什麼,根據虛擬機設計團隊的解釋,「實現經過一個類的全限定名來獲取描述此類的二進制字節流這個動做的代碼模塊」被稱爲類加載器。數據結構
類加載器雖然只用於實現類的加在動做,可是它在Java程序中起到的做用卻遠遠不限於類加載階段。編程語言
對於任意一個類,都須要由加載它的類加載器和這個類自己一同確立其在Java虛擬機中的惟一性。這就解釋了爲何上面判斷時爲出現false,由於一個使用的系統提供的類加載器,而另外一個是使用了本身編寫的加載器。ide
下面經過一個簡單的實例來還原下前面的問題:學習
package com.sherry; import java.io.InputStream; public class Loader { public static void main(String[] args) throws Exception { ClassLoader loader = new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { String className = name .substring(name.lastIndexOf('.') + 1) + ".class"; InputStream iStream = getClass().getResourceAsStream( className); if (iStream == null) { return super.loadClass(name); } byte[] b = new byte[iStream.available()]; iStream.read(b); return defineClass(name, b, 0, b.length); } catch (Exception e) { throw new ClassNotFoundException(name); } } }; Object obj = new Loader(); System.out.println("默認:" + obj.getClass()); Object myObj = loader.loadClass(Loader.class.getName()).newInstance(); System.out.println("自定義:" + myObj.getClass()); System.out.println("--------------------自定義------------------------"); System.out.print("instanceof: "); System.out.println(myObj instanceof com.sherry.Loader); System.out.println("--------------------默認-------------------------"); Object obj1 = new Loader(); System.out.print("instanceof: "); System.out.println(obj1 instanceof com.sherry.Loader); } }
運行結果:
這段代構造了一個能夠加載本身所在路徑下的class文件的類加載器,而後經過它實例化了Loader類的一個對象myObj,同時也經過默認的方法構造得到一個對象obj,從結果能夠看出,myObj這個對象確實是類com.sherry.Loader實例化的對象,但這個對象與類com.sherry.Loader作所屬類型檢查時卻返回了false。
緣由就是虛擬機中存在了兩個Loader類,一個由系統應用程序類加載器加載,另外一個由自定義加載器加載,雖然來源於同一個class文件,但確是兩個獨立的類。
到這裏基本就弄明白了咱們前面所遇到的問題,可是孔子曰:「學而不思則罔,思而不學則殆」。爲了從此在Java類加載這裏再也不有更多的問題,咱們還要進一步來了解更多關於類加載的知識。
借用書上的一句話:「代碼編譯的結果從本地機器碼轉變爲字節碼,是存儲格式的一小步,確實編程語言的一大步」。
要講解類加載機制,就不得不先了解一下類的文件結構。
想必「平臺無關性」的概念你們都很熟悉,而實平臺無關的這個理想最終也實如今了操做系統的應用層上。
Sun公司以及其它虛擬機提供商發佈了不少能夠運行在各類不一樣平臺是的虛擬機,這些虛擬機均可以載入和執行同一種平臺無關的程序存儲格式——字節碼。
字節碼就是構成這種無關性的基石。
Java虛擬機不和任何包括Java在內的語言綁定,它之與「Class文件」這種特定的二進制文件格式所關聯,任何一種功能性語言均可以表示爲一個可以被Java虛擬機所接受的有效地Class文件。
Class文件中包含了Java虛擬機指令集和符號表以及若干其它輔助信息。
Class文件是一組以8字節爲基礎單位的二進制流,各個數據項目嚴格按照順序緊密的排列在Class文件中,中間沒有任何分隔符,即Class文件是一個有強制性語法和結構化約束的。
Class文件格式採用一種相似C語言結構體的僞結構來存儲數據,這種僞結構中只有兩種數據類型:無符號數和表。
無符號數:屬於基本數據類型,能夠用來描述數字、索引引用、數量值或utf-8編碼的字符串值;
表:由多個無符號數或者其餘表構成的複合類型。
Class 文件格式
序號 | 類型 | 名稱 | 數量 | 功能 |
---|---|---|---|---|
1 | u4 | magic | 1 | 魔數,驗證 Class 文件的合法性 |
2 | u2 | minor_version | 1 | 次版本號 |
3 | u2 | major_version | 1 | 主版本號 |
4 | u2 | constant_pool_count | 1 | 常量池容量.惟一從1開始計數的;第0項常量一般爲了表示「不引用任何一個常量池項目」的含義 |
5 | cp_info | constant_pool | constant_pool_count - 1 | 常量池信息 |
6 | u2 | access_flags | 1 | 訪問標誌,用於表示一些類或接口的訪問信息。好比:是類仍是接口、訪問權限、是否爲抽象、類是否爲final |
7 | u2 | this_class | 1 | 類索引,肯定這個類的全限定名 |
8 | u2 | super_class | 1 | 父類索引,肯定父類的全限定名 |
9 | u2 | interfaces_count | 1 | 接口計數器 |
10 | u2 | interfaces | interfaces_count | 接口索引信息 |
11 | u2 | fields_count | 1 | 字段表計數器 |
12 | field_info | fields | fields_count | 字段表,用於描述接口或類中聲明的變量 |
13 | u2 | methods_count | 1 | 方法表計數器 |
14 | method_info | methods | methods_count | 方法表,用於描述類中方法信息以及編譯器自動添加的方法信息,父類中的方法若是沒有被複寫,則不會出現 |
15 | u2 | attributes_count | 1 | 屬性表計數器 |
16 | attribute | attributes | attributes_count | 屬性表,在Class文件、字段表、方法表均可以有屬性表集合,用於表述某些場景下專有的信息 |
其中u一、u2等表示1個字節、2個字節的無符號數;_info結尾的表示一個表。
功能欄簡要介紹了每一個字段的意義,關於這些字段的具體含義,會在接下來的文章中仔細介紹。
Class文件是Java虛擬機執行引擎的數據入口,也是Java技術體系的基礎構成之一,這是進一步理解虛擬機執行引擎的基礎知識。
虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校、轉換解析和初始化,最終造成了能夠被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。
類從被加載到虛擬機內存開始,到卸載出內存爲止,它的整個生命週期包括:加載、驗證、準備、解析、初始化、使用、卸載7個階段,其中驗證、準備、解析部分統稱爲鏈接。
其中加載、驗證、準備、初始化和卸載這5個階段的開始順序是肯定的,而解析階段則不必定,某些時候能夠顯出石化後解析(爲了支持Java語言的動態綁定)。
在加載階段,虛擬機須要完成如下三件事情:
經過一個類的全限定名來獲取定義這個類的二進制字節流;
將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構;
在內存中生成一個表明這個類的java.lang.Class對象,做爲方法區這個類的各類數據訪問的入口。
其中「經過一個類的全限定名來獲取定義這個類的二進制字節流」不侷限於從一個Class文件中獲取,還能夠從ZIP包中讀取、從網絡中獲取、運行時計算獲取、數據庫獲取等出多途徑。
這一階段的目的是確保Class文件中的字節流包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。
驗證階段大體上會完成下面4個檢驗動做:
文件格式驗證——是否符合Class文件格式的規範;
元數據驗證——對字節碼描述的信息進行語義分析,保證其描述的信息符合Java語言規範的要求;
字節碼驗證——目的是經過數據流和控制流的分析,肯定程序語義是合法的、符合邏輯的;
符號引用驗證——校驗發生在虛擬機將符號引用轉化爲直接引用的時候,是對類自身之外的信息進行匹配性校驗。
準備階段是正式爲類變量分配內存並設置類變量初始值的階段 ,這些變量所使用的內存都將在方法區中進行分配。
注意這裏分配的只是類變量(被static修飾的變量),而不包括實例變量,實例變量將會在對象實例化時隨着對象一塊兒分配在Java堆中。
這裏所謂的賦的初始值通常是指數據類型的零值。
解析階段是虛擬機將常量池內的符號引用替換爲直接引用的過程;
符號引用:符號引用以一組符號來描述所引用的目標;
直接引用:至及誒引用能夠是直接指向目標的指針、相對偏移量或是一個能簡介定位到目標的句柄。
解析動做主要是針對類或接口、字段、接口方法、類方法、方法類型、方法句柄和調用點限定符7類符號引用進行。
到了初始化階段,才真正開始執行類中定義的Java程序代碼;在準備階段中,變量已經賦過一次系統要求的初始值,而在初始化階段,則根據程序員經過程序指定的主觀計劃去初始化變量和其它資源。
上面簡要的介紹了Class文件的結構、如何將類加載到虛擬機中這些問題,接下來一篇會對其中的細節再作深刻介紹並介紹一下Java中的雙親委派模型。