n class裝載驗證流程java
n 什麼是類裝載器ClassLoadernode
n JDK中ClassLoader默認設計模式編程
n 打破常規模式bootstrap
n 熱替換設計模式
n 加載數組
n 連接安全
– 驗證數據結構
– 準備app
– 解析框架
n 初始化
n 裝載類的第一個階段
n 取得類的二進制流[z1]
n 轉爲方法區數據結構
n 在Java堆中生成對應的java.lang.Class對象
n 連接 -> 驗證
– 目的:保證Class流的格式是正確的
文件格式的驗證
n 是否以0xCAFEBABE開頭
n 版本號是否合理
元數據驗證
n 是否有父類
n 繼承了final類?
n 非抽象類實現了全部的抽象方法
1, 什麼是元數據:
舉例:任何文件系統中的數據分爲數據和元數據。數據是指普通文件中的實際數據,而元數據指用來描述一個文件的特徵的系統數據,諸如訪問權限、文件擁有者以及文件數據塊的分佈信息(inode...)等等。在集羣文件系統中,分佈信息包括文件在磁盤上的位置以及磁盤在集羣中的位置。用戶須要操做一個文件必須首先獲得它的元數據,才能定位到文件的位置而且獲得文件的內容或相關屬性。
2,元數據最大的好處是,它使信息的描述和分類能夠實現格式化,從而爲機器處理創造了可能。
3, 元數據概念:元數據是關於數據的數據。在編程語言上下文中,元數據是添加到程序元素如方法、字段、類和包上的額外信息。對數據進行說明描述的數據。
4,元數據的做用: 通常來講,元數據能夠用於建立文檔(根據程序元素上的註釋建立文檔),跟蹤代碼中的依賴性(可聲明方法是重載,依賴父類的方法),執行編譯時檢查(可聲明是否編譯期檢測),代碼分析。以下:
1)編寫文檔:經過代碼裏標識的元數據生成文檔
2)代碼分析:經過代碼裏標識的元數據對代碼進行分析
3).編譯檢查:經過代碼裏標識的元數據讓編譯器能實現基本的編譯檢查
五、java平臺元數據
註解Annotation就是java平臺的元數據,是 J2SE5.0新增長的功能,該機制容許在Java 代碼中添加自定義註釋,並容許經過反射(reflection),以編程方式訪問元數據註釋。經過提供爲程序元素(類、方法等)附加額外數據的標準方法,元數據功能具備簡化和改進許多應用程序開發領域的潛在能力,其中包括配置管理、框架實現和代碼生成。
Annotation不直接影響程序的語義。然而,開發和部署工具能夠讀取這些註釋,並以某種形式處理這些註釋,可能生成其餘 Java源程序、XML配置文件或者要與包含註釋的程序一塊兒使用的其餘組件,從而影響運行狀態的程序的語義。註釋能夠從源代碼中讀取,從編譯後的.class文件中讀取,也能夠經過反射機制在運行時讀取。
一、Annotation具備如下的一些特色:
元數據以標籤的形式存在於Java代碼中。
元數據描述的信息是類型安全的,即元數據內部的字段都是有明確類型的。
元數據須要編譯器以外的工具額外的處理用來生成其它的程序部件。
元數據能夠只存在於Java源代碼級別,也能夠存在於編譯以後的Class文件內部。
二、在JDK5.0前沒出現語言級的元數據機制Annotation,java元數據的解決方案:
在註解誕生以前,程序的元數據存在的形式僅限於xml 部署描述文件、java註釋或javadoc,但註解能夠提供更多功能,它不只包含元數據,還能做用於運行期,註解解析器可以使用註解決定處理流程
字節碼驗證 (很複雜)
n 運行檢查
n 棧數據類型和操做碼數據參數吻合
n 跳轉指令指定到合理的位置
符號引用驗證
n 常量池中描述類是否存在
n 訪問的方法或字段是否存在且有足夠的權限
n 符號引用是基於字符串的。
n 連接 -> 準備
– 分配內存,併爲類設置初始值 (方法區中)
• public static int v=1;
• 在準備階段中,v會被設置爲0
• 在初始化的<clinit>中才會被設置爲1
• 對於static final類型,在準備階段就會被賦上正確的值
• public static final int v=1;
n 連接 -> 解析
– 將符號引用替換爲直接引用
– 符號引用是一個字符串,被引用對象不必定被加載
– 直接引用是指向一個對象的指針或者對象的地址偏移量,被引用的對象必定在內存
符號引用和直接引用:
簡單來說符號引用就是一個字符串常量。好比:好比說我這個類的超類是java.lang.object.符號引用就是說,我在我這個class的常量池裏面,我有一個字符串,字符串的內容就是java.lang.object.這就是符號引用。符號引用並不能直接的引用到要引用的內容,他只是一種表示的方式。要真正可以直接使用這個引用,那麼咱們須要直接引用,直接引用就是指針指向一個內存地址或者地址偏移量(偏移量必定是有一個基地址,而後相對這個基地址偏移多少獲得的那個地址),直接指向到要引用的內容。引用對象必定在內存,而符號引用,引用對象不必定被加載。
1. 符號引用(Symbolic References):
符號引用以一組符號來描述所引用的目標,符號能夠是任何形式的字面量[z2] ,只要使用時可以無歧義的定位到目標便可。例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等類型的常量出現。符號引用與虛擬機的內存佈局無關,引用的目標並不必定加載到內存中。在Java中,一個java類將會編譯成一個class文件。在編譯時,java類並不知道所引用的類的實際地址,所以只能使用符號引用來代替。好比org.simple.People類引用了org.simple.Language類,在編譯時People類並不知道Language類的實際內存地址,所以只能使用符號org.simple.Language(假設是這個,固然實際中是由相似於CONSTANT_Class_info[z3] 的常量來表示的)來表示Language類的地址。各類虛擬機實現的內存佈局可能有所不一樣,可是它們能接受的符號引用都是一致的,由於符號引用的字面量形式明肯定義在Java虛擬機規範的Class文件格式中。
2. 直接引用:直接引用能夠是
(1)直接指向目標的指針(好比,指向「類型」【Class對象】、類變量、類方法的直接引用多是指向方法區的指針)
(2)相對偏移量(好比,指向實例變量、實例方法的直接引用都是偏移量),偏移量就是內存地址。
(3)一個能間接定位到目標的句柄
直接引用是和虛擬機的佈局相關的,同一個符號引用在不一樣的虛擬機實例上翻譯出來的直接引用通常不會相同。若是有了直接引用,那引用的目標一定已經被加載入內存中了。
n 執行 類構造器<clinit>
– static變量的 賦值語句
– static{}語句
n 子類的<clinit>調用前保證父類的<clinit>被調用
n <clinit>是線程安全的
n Java.lang.NoSuchFieldError錯誤可能在什麼階段拋出?
域不存在錯誤。當應用試圖訪問或者修改某類的某個域,而該類的定義中沒有該域的定義時拋出該錯誤。
很顯然是在連接的驗證階段的符號引用驗證時會拋出這個異常,或者NoSuchMethodError等異常。
n ClassLoader是一個抽象類
n ClassLoader的實例將讀入Java字節碼將類裝載到JVM中
n ClassLoader能夠定製,知足不一樣的字節碼流獲取方式
n ClassLoader負責類裝載過程當中的加載階段
– public Class<?> loadClass(String name) throws ClassNotFoundException
• 載入並返回一個Class(猜想它內部調用了defineClass方法)
– protected final Class<?> defineClass(byte[] b, int off, int len)
• 定義一個類,不公開調用
• 真正完成類的加載工做是經過調用 defineClass來實現的;將字節數組轉爲方法區數據結構,在Java堆中生成對應的java.lang.Class對象等
– protected Class<?> findClass(String name) throws ClassNotFoundException
• loadClass會調用該方法,自定義ClassLoader的推薦作法,這個方法在jdk中是空實現,在jdk自帶的加載器都沒法加載該類時,會調用這個方法。
– protected final Class<?> findLoadedClass(String name)
• 尋找已經加載的類
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); } |
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader }
if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name);
// this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } } |
加載有三步:
1, 找到或獲得.class字節碼文件(或字節碼內容)。
2, 經過流,將字節碼內容讀取到內存中,並轉化爲字節數組。(這一步已經完成了加載到內存的操做)
3, 將該字節數組做爲參數傳入defineClass方法,在堆中定義該類的Class對象。(真正完成類的加載工做是經過調用 defineClass來實現的;將字節數組轉爲方法區數據結構,在Java堆中生成對應的java.lang.Class對象等)
n BootStrap ClassLoader (啓動ClassLoader)
n Extension ClassLoader (擴展ClassLoader)
n App ClassLoader (應用ClassLoader/系統ClassLoader)
n Custom ClassLoader(用戶自定義ClassLoader)
n 每一個ClassLoader都有一個Parent做爲父親(除了根加載器BootStrap ClassLoader,它是最頂級的)
n Thread. setContextClassLoader(ClassLoader cl)
– 上下文加載器
– 是一個角色
– 用以解決頂層ClassLoader沒法訪問底層ClassLoader加載的類的問題
– 基本思想是,在頂層ClassLoader中,傳入底層ClassLoader的實例
– 將類加載器對象在線程中傳遞,當咱們要加載一個類時,咱們能夠從線程中獲取這個類加載器來加載,這樣能夠保證當咱們想要用同一個加載器加載那些在不一樣類加載器做用區域的類。
示例:
代碼來自於
javax.xml.parsers.FactoryFinder
展現如何在啓動類加載器加載AppLoader的類(上下文ClassLoader能夠突破雙親模式的侷限性)
static private Class getProviderClass(String className, ClassLoader cl, boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException { try { if (cl == null) { if (useBSClsLoader) { return Class.forName(className, true, FactoryFinder.class.getClassLoader()); } else { cl = ss.getContextClassLoader(); if (cl == null) { throw new ClassNotFoundException(); } else { return cl.loadClass(className); //使用上下文ClassLoader } } } else { return cl.loadClass(className); } } catch (ClassNotFoundException e1) { if (doFallback) { // Use current class loader - should always be bootstrap CL return Class.forName(className, true, FactoryFinder.class.getClassLoader()); } else { throw e1; } } } |
|
|
|
ClassLoader getContextClassLoader() throws SecurityException{ return (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { ClassLoader cl = null; //try { cl = Thread.currentThread().getContextClassLoader(); //} catch (SecurityException ex) { }
if (cl == null) cl = ClassLoader.getSystemClassLoader();
return cl; } }); } |
|
n 雙親模式的破壞
– 雙親模式是默認的模式,但不是必須這麼作
– Tomcat的WebappClassLoader 就會先加載本身的Class,找不到再委託parent
– OSGi的ClassLoader造成網狀結構,根據須要自由加載Class
自定義OrderClassLoader的部分實現:
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class re=findClass(name);//先嚐試本身加載 if(re==null){ System.out.println(「沒法載入類:」+name+「 須要請求父加載器"); return super.loadClass(name,resolve);//加載不了再委託父加載器加載 } return re; } |
protected Class<?> findClass(String className) throws ClassNotFoundException { Class clazz = this.findLoadedClass(className); if (null == clazz) { try { String classFile = getClassFile(className); FileInputStream fis = new FileInputStream(classFile); FileChannel fileC = fis.getChannel(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); WritableByteChannel outC = Channels.newChannel(baos); ByteBuffer buffer = ByteBuffer.allocateDirect(1024); 省略部分代碼 fis.close(); byte[] bytes = baos.toByteArray(); clazz = defineClass(className, bytes, 0, bytes.length); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return clazz; } |
使用OrderClassLoader:
OrderClassLoader myLoader=new OrderClassLoader("D:/tmp/clz/"); Class clz=myLoader.loadClass("geym.jvm.ch6.classloader.DemoA"); System.out.println(clz.getClassLoader()); System.out.println("==== Class Loader Tree ===="); ClassLoader cl=myLoader; while(cl!=null){ System.out.println(cl); cl=cl.getParent(); } |
輸出結果:
java.io.FileNotFoundException: D:\tmp\clz\java\lang\Object.class (系統找不到指定的路徑。[z4] ) at java.io.FileInputStream.open(Native Method) ..... at geym.jvm.ch6.classloader.ClassLoaderTest.main(ClassLoaderTest.java:7) 沒法載入類:java.lang.Object須要請求父加載器[z5] geym.jvm.ch6.classloader.OrderClassLoader@18f5824 ==== Class Loader Tree ==== geym.jvm.ch6.classloader.OrderClassLoader@18f5824 sun.misc.Launcher$AppClassLoader@f4f44a sun.misc.Launcher$ExtClassLoader@1d256fa |
若是OrderClassLoader不重載loadClass(),只重載findClass,那麼程序輸出爲
sun.misc.Launcher$AppClassLoader@b23210[z6] ==== Class Loader Tree ==== geym.jvm.ch6.classloader.OrderClassLoader@290fbc sun.misc.Launcher$AppClassLoader@b23210 sun.misc.Launcher$ExtClassLoader@f4f44a |
不重載loadClass()方法,會使用雙親的模式,就會先讓AppLoader加載
n 含義:
– 當一個class被替換後,系統無需重啓,替換的類當即生效
– 例子:
• geym.jvm.ch6.hot.CVersionA
public class CVersionA { public void sayHello() { System.out.println("hello world! (version A)"); } } |
n DoopRun 不停調用CVersionA . sayHello()方法,所以有輸出:
– hello world! (version A)
n 在DoopRun 的運行過程當中,替換CVersionA 爲:
public class CVersionA { public void sayHello() { System.out.println("hello world! (version B)"); } } |
n 替換後, DoopRun 的輸出變爲
– hello world! (version B)
[z1]1,class字節碼文件是根據JVM虛擬機規範中規定的字節碼組織規則生成的。
2,字節是電腦裏的數據量單位。字節碼是一種中間碼,它比機器碼更抽象。它常常被看做是包含一個執行程序的二進制文件,更像一個對象模型。字節碼被這樣叫是由於一般每一個 opcode 是一字節長(java中自個字節佔8個比特位)。
3,機器碼就是說計算機能讀懂的代碼,簡單點說就是給計算機執行的二進制代碼.字節碼,是JAVA語言專有的,它是讓JVM來執行的二進制代碼。雖然都是二進制代碼,可是因爲執行它的哥們不同,因此它們存在一些指令集上的區別。
4,機器碼,徹底依附硬件而存在,而且不一樣硬件因爲內嵌指令集不一樣,即便相同的0 1代碼 意思也多是不一樣的,換句話說,根本不存在跨平臺性,好比:不一樣型號的CPU,你給他個指令10001101,他們可能會解析爲不一樣的結果。
5,咱們知道JAVA是跨平臺的,爲何呢?由於他有一個jvm,不論那種硬件,只要你裝有jvm,那麼他就認識這個JAVA字節碼,至於底層的機器碼,咱不用管,有jvm搞定,他會把字節碼再翻譯成所在機器認識的機器碼。
6,機器碼就是0101這樣的二進制代碼,而二進制碼要想操做機器就須要相應的指令系統。而指令系統就是對二進制代碼所表示意思的規定,好比0表示某個開關關,1表示某個開關開這樣的.關於這一部分屬於硬件部分了。
Java字面量(Java直接量)
int i = 1;把整數1賦值給int型變量i,整數1就是Java字面量,
一樣,String s = "abc";中的abc也是字面量。
1,常量池中的CONSTANT_Class_info, 能夠看作是一個CONSTANT_Class數據類型的一個實例。 他是對類或者接口的符號引用。 它描述的能夠是當前類型的信息, 也能夠描述對當前類的引用, 還能夠描述對其餘類的引用。 也就是說, 若是訪問了一個類字段, 或者調用了一個類的方法, 對這些字段或方法的符號引用, 必須包含它們所在的類型的信息,CONSTANT_Class_info就是對字段或方法符號引用中類型信息的描述。
2,CONSTANT_Class_info的第一個字節是tag, 值爲7, 也就是說, 當虛擬機訪問到一個常量池中的數據項, 若是發現它的tag值爲7, 就能夠判斷這一個CONSTANT_Class_info 。 tag下面的兩個字節是一個叫作name_index的索引值, 它指向一個CONSTANT_Utf8_info, 這個CONSTANT_Utf8_info中存儲了CONSTANT_Class_info要描述的類型的全限定名。
[z4]由於先從OrderClassLoader加載,找不到父類Object,以後使用appLoader加載Object
[z5]DemoA在ClassPath中,但由OrderClassLoader加載,由於方法中是先由本身加載,加載不了再父加載器加載。
[z6]DemoA由AppClassLoader加載