預約義類加載器(三種):java
啓動(Bootstrap)類加載器:
是用本地代碼實現的類裝入器,它負責將<Java_Runtime_Home>/lib下面的類庫加載到內存中(好比rt.jar)。
因爲引導類加載器涉及到虛擬機本地實現細節,開發者沒法直接獲取到啓動類加載器的引用,因此不容許直接經過引用進行操做。
擴展擴展(Extension)類加載器:
是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實現的。它負責將< Java_Runtime_Home >/lib/ext或者由系統變量java.ext.dir指定位置中的類庫加載到內存中。開發者能夠直接使用標準擴展類加載器。
系統(System)類加載器:安全
是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)實現的。它負責將系統類路徑(CLASSPATH)中指定的類庫加載到內存中。開發者能夠直接使用系統類加載器。jvm
雙親委派機制:
某個特定的類加載器在接到加載類的請求時,首先將加載任務委託給父類加載器,依次遞歸,若是父類加載器能夠完成類加載任務,就成功返回;只有父類加載器沒法完成此加載任務時,才本身去加載。這是一種代理方式。spa
雙親委派意義:
系統類防止內存中出現多份一樣的字節碼
保證Java程序安全穩定運行.net
線程上下文類加載器(特殊):
破壞了「雙親委派模型」,能夠在執行線程中拋棄雙親委派加載鏈模式,使程序能夠逆向使用類加載器。
類 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用來獲取和設置線程的上下文類加載器。
若是沒有經過 setContextClassLoader(ClassLoader cl)方法進行設置的話,線程將繼承其父線程的上下文類加載器。Java 應用運行的初始線程的上下文類加載器是系統類加載器。在線程中運行的代碼能夠經過此類加載器來加載類和資源。線程
1.當高層提供了統一接口讓低層去實現,同時又要是在高層加載(或實例化)低層的類時,必須經過線程上下文類加載器來幫助高層的ClassLoader找到並加載該類。
2.當使用本類託管類加載,然而加載本類的ClassLoader未知時,爲了隔離不一樣的調用者,能夠取調用者各自的線程上下文類加載器代爲託管。代理
幾點問題:code
啓動(Bootstrap)類加載器它用來加載 Java 的核心庫,是用原生代碼來實現的,並不繼承自 java.lang.ClassLoader。對象
Java 虛擬機是如何斷定兩個 Java 類是相同的?blog
Java 虛擬機不只要看類的全名是否相同,還要看加載此類的類加載器是否同樣。只有二者都相同的狀況,才認爲兩個類是相同的。即使是一樣的字節代碼,被不一樣的類加載器加載以後所獲得的類,也是不一樣的。
eg:
public void testClassIdentity() { String classDataRootPath = "C:\\workspace\\Classloader\\classData"; FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath); FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath); String className = "com.example.Sample"; try { Class<?> class1 = fscl1.loadClass(className); Object obj1 = class1.newInstance(); Class<?> class2 = fscl2.loadClass(className); Object obj2 = class2.newInstance(); Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class); setSampleMethod.invoke(obj1, obj2); } catch (Exception e) { e.printStackTrace(); } }
代碼中使用了類 FileSystemClassLoader的兩個不一樣實例來分別加載類 com.example.Sample,獲得了兩個不一樣的 java.lang.Class的實例,接着經過 newInstance()方法分別生成了兩個類的對象 obj1和 obj2,最後經過 Java 的反射 API 在對象 obj1上調用方法 setSample,試圖把對象 obj2賦值給 obj1內部的 instance對象
運行結果
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at classloader.ClassIdentity.testClassIdentity(ClassIdentity.java:26)
at classloader.ClassIdentity.main(ClassIdentity.java:9)
Caused by: java.lang.ClassCastException: com.example.Sample
cannot be cast to com.example.Sample
at com.example.Sample.setSample(Sample.java:7)
... 6 more
給出的運行結果能夠看到,運行時拋出了 java.lang.ClassCastException異常。雖然兩個對象 obj1和 obj2的類的名字相同,可是這兩個類是由不一樣的類加載器實例來加載的,所以不被 Java 虛擬機認爲是相同的。
Java類的生命週期:
類從被加載到虛擬機內存中開始,到卸載出內存爲止,它的整個生命週期包括:加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)7個階段。其中準備、驗證、解析3個部分統稱爲鏈接(Linking)。
加載(Loading):
就是將源文件的class文件找到類的信息將其加載到方法區中,
而後在堆區中實例化一個java.lang.Class對象,做爲方法區中這個類的信息的入口。
鏈接(Linking):
驗證:肯定該類是否符合java語言的規範,有沒有屬性和行爲的重複,繼承是否合理,總之,就是保證jvm可以執行
準備:主要作的就是爲由static修飾的成員變量分配內存,並設置默認的初始值
(1.八種基本數據類型默認的初始值是0
2.引用類型默認的初始值是null
3.有static final修飾的會直接賦值,例如:static final int x=10;則默認就是10.)
解析:這一階段的任務就是把常量池中的符號引用轉換爲直接引用,說白了就是jvm會將全部的類或接口名、字段名、方法名轉換爲具體的內存地址。
初始化(Initialization)
這個階段就是將靜態變量(類變量)賦值的過程,即只有static修飾的才能被初始化,執行的順序就是:
父類靜態域或着靜態代碼塊,而後是子類靜態域或者子類靜態代碼塊
使用(Using)
在類的使用過程當中依然存在三步:對象實例化、垃圾收集、對象終結
卸載(Unloading)
類的生命週期走到了最後一步,程序中再也不有該類的引用,該類也就會被JVM執行垃圾回收,今後生命結束。
參考文章:
https://www.ibm.com/developerworks/cn/java/j-lo-classloader/
http://blog.csdn.net/yangcheng33/article/details/52631940