1、類加載簡介java
類加載機制:JVM把class文件加載到內存,並對數據進行校驗、解析和初始化,最終造成JVM能夠直接使用的Java類型的過程。apache
2、bootstrap
加載:將class文件字節碼內容經過類加載器加載到內存中,並將這些靜態數據轉換成方法區中的運行時數據結構,在堆中生成一個表明這個類的java.lang.Class對象,做爲方法區類數據的訪問入口(外部能夠經過Class對象,做爲操做類的入口)。這個過程須要類加載器參與。數組
連接:將Java類的二進制代碼合併到JVM的運行狀態之中的過程。【驗證:確保加載的類信息符合JVM規範,沒有安全方面的問題。準備:正式爲類變量(static變量)分配內存並設置類變量初始值的階段,這些內存都將在方法區中進行分配。解析:虛擬機常量池內的符號引用替換爲直接引用的過程。】緩存
初始化:1.初始化階段是執行類構造器<clinit>()方法的過程。類構造器<clinit>()方法是由編譯器自動收集類中的全部類變量的賦值動做和靜態語句塊(static塊)中的語句合併產生的。 2.當初始化一個類的時候,若是發現其父類尚未進行過初始化,則須要先觸發其父類的初始化。 3.虛擬機會保證一個類的<clinit>()方法在多線程環境中被正確加鎖和同步。當訪問一個Java類的靜態域時,只有真正聲明這個域的類纔會被初始化。tomcat
類的主動引用(必定會發生類的初始化)安全
- new 一個類的對象服務器
- 調用類的靜態成員(除了final常量)和靜態方法網絡
- 使用java.lang.reflect包的方法對類進行反射調用數據結構
- 當虛擬機啓動,java Hello,則必定會初始化Hello類,說白了就是先啓動main方法所在的類
- 當初始化一個類,若是其父類沒有被初始化,則先會初始化他的父類
類的被動引用(不會發生類的初始化)
- 當訪問一個靜態域時,只有真正聲明這個域的類纔會被初始化
. 經過子類引用父類的靜態變量,不會致使子類初始化
- 經過數組定義類引用,不會觸發此類的初始化
- 引用常量不會觸發此類的初始化(常量在編譯階段就存入調用類的常量池中了)
3、
public class Demo01 { public static void main(String[] args) { System.out.println("初始化Demo01"); //A a = new A(); // System.out.println("width= " + A.width); // System.out.println(A.MAX); // System.out.println(Child_A.width); A[] arr = new A[]{}; } } class A extends Father_A { static { System.out.println("靜態初始化塊"); width = 200; } public static int width = 100; public static final int MAX = 100; public A() { System.out.println("構造函數初始化中"); } { System.out.println("初始化塊"); width = 300; } } class Father_A { static { System.out.println("靜態初始化塊——Father_A"); } } class Child_A extends A { static { System.out.println("靜態初始化Child_A"); } }
4、深刻類加載器
類加載器的做用:將class文件字節碼內容加載到內存中,並將這些靜態數據轉換成方法區中的運行時數據結構,在堆中生成一個表明這個類的java.lang.Class對象,做爲方法區類數據的訪問入口。
類緩存:標準的JavaSE類加載器能夠按要求查找類,但一旦某個類被加載到類加載器中,它將維持加載(緩存)一段時間。不過,JVM垃圾收集器能夠回收這些Class對象。
類加載器的層次結構(樹狀結構)
引導類加載器(bootstrap class loader)
- 它用來加載Java的核心庫(JAVA_HOME/jre/lib/rt.jar,或sum.boot.class.path路徑下的內容),是用原生代碼來實現的,並不繼承自java.lang.ClassLoader。
- 加載擴展類和應用程序類加載器,並指定他們的父類加載器。
擴展類加載器(extensions class loader)
- 用來加載Java的擴展庫(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路徑下的內容)。Java虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄路面查找並加載Java類。
- 由sum.misc.Launcher$ExtClassLoader實現
應用程序類加載器(application class loader)
- 它根據Java應用的類路徑(classpath,java.class.path路徑)。通常來講,Java應用的類都是由它來完成加載的。
- 由sun.misc.Launcher$AppClassLoader實現。
自定義類加載器
自定義類加載器的流程
- 繼承:java.lang.ClassLoader
- 首先檢查請求的類型是否已經被這個類裝載器裝載到命名空間中了,若是已經裝載,直接返回;
- 委派類加載請求給父類加載器,若是父類加載器可以完成,則返回父類加載器加載的Class實例;
- 調用本類加載器的findClass(...)方法,試圖獲取對應的字節碼,若是獲取的到,則調用defineClass(...)導入類型到方法區;若是獲取不到對應的字節碼或者其它緣由失敗,返回異常給loadClass(...),loadClass(...)轉拋異常,終止加載過程;
- 注意:被兩個類加載器加載的同一個類,JVM不認爲是相同的類。
文件類加載器
import org.apache.commons.io.IOUtils; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; public class FileSystemClassLoader extends ClassLoader { private String rootDir; public FileSystemClassLoader(String rootDir) { this.rootDir = rootDir; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class<?> clazz = findLoadedClass(name); /** * 先查詢有沒有加載過這個類,若是已經加載,則直接返回加載好的類;若是沒有,則加載新的類 */ if (clazz != null) { return clazz; } else { /** * 委派類加載請求給父類加載器 */ ClassLoader parent = this.getParent(); try { clazz = parent.loadClass(name); } catch (Exception e) { } if (clazz != null) { return clazz; } else { byte[] classData = getClassData(name); if (classData == null) { throw new ClassNotFoundException(); } else { clazz = defineClass(name, classData,0, classData.length); } } } return clazz; } private byte[] getClassData(String classname) { String path = rootDir + "/" + classname.replace(".", "/") + ".class"; /** * 注:IOUtils,可使用它將流中的數組轉換成字節數組 */ InputStream is = null; ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { is = new FileInputStream(path); byte[] buffer = new byte[1024]; int temp = 0; while ((temp = is.read(buffer)) != -1) { baos.write(buffer, 0, temp); } return baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); return null; } finally { try { if (is != null) { is.close(); } if (baos != null) { baos.close(); } }catch (IOException e) { e.printStackTrace(); } } /* InputStream is = null; try { is = new FileInputStream(path); byte[] bytes = IOUtils.toByteArray(is); return bytes; } catch (Exception e) { e.printStackTrace(); return null; } finally { try { if (is != null) { is.close(); } } catch (Exception e) { e.printStackTrace(); } }*/ } }
ipublic class Demo02 { public static void main(String[] args) throws ClassNotFoundException { FileSystemClassLoader classLoader = new FileSystemClassLoader("/Users/Work/mimidai/mimidai-test"); FileSystemClassLoader classLoader2 = new FileSystemClassLoader("/Users/Work/mimidai/mimidai-test"); Class<?> clazz = classLoader.loadClass("HelloWordI"); Class<?> clazz1 = classLoader.loadClass("HelloWordI"); Class<?> clazz2 = classLoader2.loadClass("HelloWordI"); Class<?> clazz3 = classLoader2.loadClass("java.lang.String"); Class<?> clazz4 = classLoader2.loadClass("com.mimidai.test2.Demo01"); System.out.println(clazz.hashCode()); System.out.println(clazz1.hashCode()); System.out.println(clazz2.hashCode()); System.out.println(clazz.getClassLoader()); System.out.println(clazz2.getClassLoader()); System.out.println(clazz3.getClassLoader()); System.out.println(clazz4.getClassLoader()); } }
輸出結果
363771819
363771819
2065951873
com.mimidai.test2.FileSystemClassLoader@16b98e56
com.mimidai.test2.FileSystemClassLoader@27d6c5e0
null
sun.misc.Launcher$AppClassLoader@18b4aac2
網絡類加載器
import org.apache.commons.io.IOUtils; import java.io.InputStream; import java.net.URL; public class NetClassLoader extends ClassLoader { private String rootURL; public NetClassLoader(String rootURL) { this.rootURL = rootURL; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class<?> clazz = this.findLoadedClass(name); if (clazz != null) { return clazz; } else { ClassLoader parentClassLoader = this.getParent(); try { clazz = parentClassLoader.loadClass(name); } catch (Exception e) { } if (clazz != null) { return clazz; } else { byte[] classData = getClassData(name); if (classData != null) { clazz = defineClass(name, classData,0, classData.length); return clazz; } else { throw new ClassNotFoundException(); } } } } private byte[] getClassData(String name) { String path = rootURL + name.replace(".", "/"); InputStream is = null; try { URL url = new URL(path); is = url.openStream(); byte[] clazzDate = IOUtils.toByteArray(is); return clazzDate; } catch (Exception e) { try { if (is != null) { is.close(); } } catch (Exception e1) { } return null; } } }
加密解密類加載器(取反操做,DES對稱加密解密)
5、java.lang.ClassLoader類介紹
- java.lang.ClassLoader類的基本職責就是根據一個指定的類的名稱,找到或者生成器對應的字節代碼,而後從這些字節代碼中定義出一個Java類,即java.lang.Class類的一個實例。
- 除此以外,ClassLoader還負責加載Java應用所需的資源,如圖像文件和配置文件等。
- 相關方法
- getParent() 返回該類加載器的父類加載器。
- loadClass(String name) 加載名稱爲name的類,返回的結果是java.lang.Class類的實例。
. 此方法負責加載指定名字的類,首先會從已加載的類中去尋找,若是沒有找到;從parent ClassLoader[ExtClassLoader]中加載;若是沒有加載到,則從Bootstrap ClassLoader中嘗試加載(findBootstrapClassOrNull方法),若是仍是加載失敗,則本身加載。若是還不能加載,則拋出異常ClassNotFoundException。
. 若是要改變類的加載順序能夠覆蓋此方法;
- findClass(String name) 查找名稱爲name的類,返回的結果是java.lang.Class類的實例。
- findLoadedClass(String name) 查找名稱爲name的已經被加載過的類,返回的結果是java.lang.Class類的實例。
- defineClass(byte[] b, int off, int len) 把字節數組b中的內容轉換成Java類,返回的結果是java.lang.Class類的實例。
- resolveClass(Class<?> c) 連接指定的Java類
6、類加載器的代理模式
代理模式
- 交給其餘加載器來加載指定的類
雙親委託機制
- 就是某個特定的類加載器在接到加載類的請求時,首先將加載任務委託給父類加載器,依此追溯,直到最高的爺爺輩的,若是父類加載器能夠完成類加載任務,就成功返回;只有父類加載器沒法完成此加載任務時,才本身去加載。
- 雙親委託機制是爲了保證Java核心庫的類型安全。
. 這種機制就保證不會出現用戶本身能定義java.lang.Object類的狀況。
- 類加載器除了用於加載類,也是安全的最基本的屏障。
- 雙親委託機制只是代理模式的一種
. 並非全部的類加載器都採用雙親委託機制。
. tomcat服務器類加載器也使用代理模式,所不一樣的是它是首先嚐試去加載某個類,若是找不到在代理給父類加載器。這與通常類加載器的順序是相反的。