1. 程式人生 > >JVM類加載器及Java類的生命周期

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

方法區 exception 獲取 調用 example 應用 contex java語言 ins

預定義類加載器(三種)

啟動(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.八種基本數據類型默認的初始值是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類的生命周期