JVM類加載器及Java類的生命週期

預約義類加載器(三種)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

相關文章
相關標籤/搜索