Java Class卸載與ClassLoader

JVM中的Class只有滿足以下三個條件,才能被GC回收,也就是該Class被卸載(unload):

   - 該類所有的實例都已經被GC,也就是JVM中不存在該Class的任何實例。
   - 加載該類的ClassLoader已經被GC。
   - 該類的java.lang.Class 對象沒有在任何地方被引用,如不能在任何地方通過反射訪問該類的方法

 
jsp和java類是完全不一樣的概念。
jsp->servlet 在web容器中,你的servlet是單例的,也是無狀態的,線程安全的。也就是隻有一個對象,
jsp改變以後,web容器只要把相應的servlet對象更新就好了。

而java呢?
可能這個類在你的應用中有n個實例,與這些實例單向,雙向關聯的又有n個實例。如果你修改了,這些jvm存在的老的實例對象怎麼辦????
java這類靜態語言無法實現象asp,php,jsp的效果的。


 weblogic熱部署原理

Weblogic允許在wls運行時部署組件的新版本。這個過程被稱作熱部署。因爲java classloader沒有任何一種機制來卸下一系列存在的類,也不能用類的新版本來替換老版本,爲了在一個運行的虛擬機中更新相關的類,classloader必須被替換掉。當它被替換時,它所裝載的所有類以及衍生的子classloader也要被重新裝載。這些類的所有實例也必需被重新裝載。在wls中,每一個應用組件都有一個層次化的classloaders,它們都是system classloader的子類,這種結構有助於每個應用或應用的一部分能被單獨重新加載,而不會影響其它的組件。

 

類加載器的種類:
  1. Bootstrap ClassLoader/啓動類加載器 
    主要負責jdk_home/lib目錄下的核心 api 或 -Xbootclasspath 選項指定的jar包裝入工作。
  2. Extension ClassLoader/擴展類加載器 
    主要負責jdk_home/lib/ext目錄下的jar包或 -Djava.ext.dirs 指定目錄下的jar包裝入工作。
  3. System ClassLoader/系統類加載器 
    主要負責java -classpath/-Djava.class.path所指的目錄下的類與jar包裝入工作。
  4. User Custom ClassLoader/用戶自定義類加載器(java.lang.ClassLoader的子類) 
    在程序運行期間, 通過java.lang.ClassLoader的子類動態加載class文件, 體現java動態實時類裝入特性。
類加載器的特性:

 

  1. 每個ClassLoader都維護了一份自己的名稱空間, 同一個名稱空間裏不能出現兩個同名的類。
  2. 爲了實現java安全沙箱模型頂層的類加載器安全機制, java默認採用了 " 雙親委派的加載鏈 " 結構。

 

 

自定義類加載器加載一個類的步驟
classloader-load-class
classloader-load-class
 
ClassLoader 類加載邏輯分析, 以下邏輯是除 BootstrapClassLoader 外的類加載器加載流程:
// 檢查類是否已被裝載過        
Class c = findLoadedClass(name);        
if (c == null ) {        
         // 指定類未被裝載過        
         try {        
                 if (parent != null ) {        
                         // 如果父類加載器不爲空, 則委派給父類加載        
                         c = parent.loadClass(name, false );        
                 } else {        
                         // 如果父類加載器爲空, 則委派給啓動類加載加載        
                         c = findBootstrapClass0(name);        
                 }        
         } catch (ClassNotFoundException e) {        
                 // 啓動類加載器或父類加載器拋出異常後, 當前類加載器將其        
                 // 捕獲, 並通過findClass方法, 由自身加載        
                 c = findClass(name);        
         }        
}

 

 

線程上下文類加載器
java默認的線程上下文類加載器是 系統類加載器(AppClassLoader)。
// Now create the class loader to use to launch the application        
try {        
        loader = AppClassLoader.getAppClassLoader(extcl);        
} catch (IOException e) {        
        throw new InternalError(        
"Could not create application class loader" );        
}         
     
// Also set the context class loader for the primordial thread.        
Thread.currentThread().setContextClassLoader(loader);    
 
以上代碼摘自sun.misc.Launch的無參構造函數Launch()。
使用線程上下文類加載器, 可以在執行線程中, 拋棄雙親委派加載鏈模式, 使用線程上下文裏的類加載器加載類.
典型的例子有, 通過線程上下文來加載第三方庫jndi實現, 而不依賴於雙親委派.
大部分java app服務器(jboss, tomcat..)也是採用contextClassLoader來處理web服務。
還有一些採用 hotswap 特性的框架, 也使用了線程上下文類加載器, 比如 seasar (full stack framework in japenese).
線程上下文從根本解決了一般應用不能違背雙親委派模式的問題.
使java類加載體系顯得更靈活.
隨着多核時代的來臨, 相信多線程開發將會越來越多地進入程序員的實際編碼過程中. 因此,
在編寫基礎設施時, 通過使用線程上下文來加載類, 應該是一個很好的選擇。
當然, 好東西都有利弊. 使用線程上下文加載類, 也要注意, 保證多根需要通信的線程間的類加載器應該是同一個,
防止因爲不同的類加載器, 導致類型轉換異常(ClassCastException)。

 

三.命名空間及其作用
每個類裝載器有自己的命名空間,命名空間由所有以此裝載器爲創始類裝載器的類組成。不同命名空間的兩個類是不可見的,但只要得到類所對應的Class對象的reference,還是可以訪問另一命名空間的類。
 
例2演示了一個命名空間的類如何使用另一命名空間的類。在例子中,LoaderSample2由系統類裝載器裝載,LoaderSample3由自定義的裝載器loader負責裝載,兩個類不在同一命名空間,但LoaderSample2得到了LoaderSample3所對應的Class對象的reference,所以它可以訪問LoaderSampl3中公共的成員(如age)。
例2不同命名空間的類的訪問
/*LoaderSample2.java*/

import    java.net. * ; 
import    java.lang.reflect. * ; 
public     class    LoaderSample2 { 
         public     static     void    main(String[] args) { 
                 try    { 
                        String path    =    System.getProperty( " user.dir " ); 
                        URL[] us    =    { new    URL( 
" file:// "     +    path    +     " /sub/ " )}; 
                        ClassLoader loader    =     new    URLClassLoader(us); 
                        Class c    =    loader.loadClass( " LoaderSample3 " ); 
                        Object o    =    c.newInstance(); 
                        Field f    =    c.getField( " age " ); 
                         int    age    =    f.getInt(o); 
                        System.out.println( " age is    "     +    age); 
                }    catch    (Exception e) { 
                        e.printStackTrace(); 
                } 
        } 
}

/*sub/Loadersample3.java*/

public     class    LoaderSample3 { 
         static    { 
                System.out.println( " LoaderSample3 loaded " ); 
        } 
         public     int    age    =     30 ; 
}
編譯:javac LoaderSample2.java; javac sub/LoaderSample3.java
運行:java LoaderSample2
LoaderSample3 loaded
age is 30
從運行結果中可以看出,在類LoaderSample2中可以創建處於另一命名空間的類LoaderSample3中的對象並可以訪問其公共成員age。

 

說明:如果LoaderSample3在classpath下能夠找到,則由URLClassLoader的parent loader AppClassLoader來加載,如果不在classpath下

則由URLClassLoader自己加載,即LoaderSample3.getClass().getClassLoader() 是URLClassLoader

運行時包(runtime package)
由同一類裝載器定義裝載的屬於相同包的類組成了運行時包,決定兩個類是不是屬於同一個運行時包,不僅要看它們的包名是否相同,還要看的定義類裝載器是否相同。只有屬於同一運行時包的類才能互相訪問包可見的類和成員。這樣的限制避免了用戶自己的代碼冒充核心類庫的類訪問核心類庫包可見成員的情況。假設用戶自己定義了一個類java.lang.Yes,並用用戶自定義的類裝載器裝載,由於java.lang.Yes和核心類庫java.lang.*由不同的裝載器裝載,它們屬於不同的運行時包,所以java.lang.Yes不能訪問核心類庫java.lang中類的包可見的成員。 
 
總結
命名空間並沒有完全禁止屬於不同空間的類的互相訪問,雙親委託模型加強了Java的安全,運行時包增加了對包可見成員的保護。
 
二.    擴展ClassLoader方法

我們目的是從本地文件系統使用我們實現的類裝載器裝載一個類。爲了創建自己的類裝載器我們應該擴展ClassLoader類,這是一個抽象類。我們創建一個FileClassLoader extends ClassLoader。我們需要覆蓋ClassLoader中的findClass(String name)方法,這個方法通過類的名字而得到一個Class對象。
         public    Class findClass(String name) 
        { 
                 byte [] data    =    loadClassData(name); 
                 return    defineClass(name, data,    0 , data.length); 
        }

我們還應該提供一個方法loadClassData(String name),通過類的名稱返回class文件的字 
節數組。然後使用ClassLoader提供的defineClass()方法我們就可以返回Class對象了。

public     byte [] loadClassData(String name) 
        { 
                FileInputStream fis    =     null ; 
                 byte [] data    =     null ; 
                 try    
                { 
                        fis    =     new    FileInputStream( new    File(drive    +    name    +    fileType)); 
                        ByteArrayOutputStream baos    =     new    ByteArrayOutputStream(); 
                         int    ch    =     0 ; 
                         while    ((ch    =    fis.read())    !=     - 1 ) 
                        { 
                                baos.write(ch); 
                                
                        } 
                        data    =    baos.toByteArray(); 
                }    catch    (IOException e) 
                { 
                        e.printStackTrace(); 
                } 
                 
                 return    data; 
        }

 

自定義ClassLoader實現java 熱替換:http://www.ibm.com/developerworks/cn/java/j-lo-hotswapcls/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章