標籤:nbsp sys scl https 實現 請求 emc 就是 異常
預定義類載入器(三種):
啟動(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)中指定的類庫載入到記憶體中。開發人員可以直接使用系統類別載入器。
雙親委派機制:
某個特定的類載入器在接到載入類的請求時,首先將載入任務委託給父類載入器,依次遞迴,如果父類載入器可以完成類載入任務,就成功返回;只有父類載入器無法完成此載入任務時,才自己去載入。這是一種代理方式。
雙親委派意義:
系統類別防止記憶體中出現多份同樣的位元組碼
保證Java程式安全穩定運行
線程上下文類載入器(特殊):
破壞了“雙親委派模型”,可以在執行線程中拋棄雙親委派載入鏈模式,使程式可以逆向使用類載入器。
類 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用來擷取和設定線程的上下文類載入器。
如果沒有通過 setContextClassLoader(ClassLoader cl)方法進行設定的話,線程將繼承其父線程的上下文類載入器。Java 應用啟動並執行初始線程的上下文類載入器是系統類別載入器。線上程中啟動並執行代碼可以通過此類載入器來載入類和資源。
1.當高層提供了統一介面讓低層去實現,同時又要是在高層載入(或執行個體化)低層的類時,必須通過線程上下文類載入器來協助高層的ClassLoader找到並載入該類。
2.當使用本類託管類載入,然而載入本類的ClassLoader未知時,為了隔離不同的調用者,可以取調用者各自的線程上下文類載入器代為託管。
幾點問題:
啟動(Bootstrap)類載入器它用來載入 Java 的核心庫,是用原生代碼來實現的,並不繼承自 java.lang.ClassLoader。
JAVA 虛擬機器是如何判定兩個 Java 類是相同的?
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.八種基礎資料型別 (Elementary Data Type)預設的初始值是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
JVM類載入器及Java類的生命週期