国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
Java虛擬機類型卸載和類型更新解析
userphoto

2011.12.14

關注

Java虛擬機類型卸載和類型更新解析


轉自:http://www.blogjava.net/zhuxing/archive/2008/07/24/217285.html

 

【摘要】
        前面系統(tǒng)討論過java類型加載(loading)的問題,在這篇文章中簡要分析一下java類型卸載(unloading)的問題,并簡要分析一下如何解決如何運行時加載newly compiled version的問題。

【相關規(guī)范摘要】
    
首先看一下,關于java虛擬機規(guī)范中時如何闡述類型卸載(unloading)的:
    A class or interface may be unloaded if and only ifits class loader is unreachable. The bootstrap class loader is alwaysreachable; as a result
, system classes may neverbe unloaded.
    Java虛擬機規(guī)范中關于類型卸載的內(nèi)容就這么簡單兩句話,大致意思就是:只有當加載該類型的類加載器實例(非類加載器類型) unreachable狀態(tài)時,當前被加載的類型才被卸載.啟動類加載器實例永遠為reachable狀態(tài),由啟動類加載器加載的類型可能永遠不會被卸載.

    
我們再看一下Java語言規(guī)范提供的關于類型卸載的更詳細的信息(部分摘錄)
    
//摘自JLS 12.7 Unloading of Classesand Interfaces
    1、An implementation of the Java programming language mayunload classes.
    2
Class unloading is anoptimization that helps reduce memory use. Obviously,thesemantics of a program should not depend  on whether and how a systemchooses to implement an optimization such as class unloading.
    3
Consequently,whether a class or interface has been unloaded or not should betransparent to a program

    
通過以上我們可以得出結論: 類型卸載(unloading)僅僅是作為一種減少內(nèi)存使用的性能優(yōu)化措施存在的,具體和虛擬機實現(xiàn)有關,對開發(fā)者來說是透明的.

    
縱觀java語言規(guī)范及其相關的API規(guī)范,找不到顯示類型卸載(unloading)的接口, 換句話說: 
    
1、一個已經(jīng)加載的類型被卸載的幾率很小至少被卸載的時間是不確定的
    2
、一個被特定類加載器實例加載的類型運行時可以認為是無法被更新的

【類型卸載進一步分析】
     前面提到過,如果想卸載某類型,必須保證加載該類型的類加載器處于unreachable狀態(tài),現(xiàn)在我們再看看有關unreachable狀態(tài)的解釋:
    1
、A reachable object is anyobject that can be accessed in any potential continuing computation from anylive thread.
    2
、finalizer-reachable: Afinalizer-reachable object can be reached from some finalizable object throughsome chain of references, but not from any live thread. An unreachable objectcannot be reached by either means.

    
某種程度上講,在一個稍微復雜的java應用中,我們很難準確判斷出一個實例是否處于unreachable狀態(tài),所    以為了更加準確的逼近這個所謂的unreachable狀態(tài),我們下面的測試代碼盡量簡單一點.
    
    
【測試場景一】使用自定義類加載器加載, 然后測試將其設置為unreachable的狀態(tài)
    
說明:
    1
、自定義類加載器(為了簡單起見, 這里就假設加載當前工程以外D盤某文件夾的class)
    2
、假設目前有一個簡單自定義類型MyClass對應的字節(jié)碼存在于D/classes目錄下
    

public class MyURLClassLoader extends URLClassLoader { 
  
public MyURLClassLoader() { 
     
super(getMyURLs()); 
   } 

  
private static URL[] getMyURLs() { 
   
try { 
      
return new URL[]{new File ("D
/classes/").toURL()}; 
    } 
catch (Exception e) { 
       e.printStackTrace(); 
      
return null
    } 
  } 

 

 1 public class Main { 
 2     public static void main(String[] args) { 
 3       try { 
 4          MyURLClassLoader classLoader = new MyURLClassLoader(); 
 5          Class classLoaded = classLoader.loadClass("MyClass"); 
 6          System.out.println(classLoaded.getName()); 
 7 
 8          classLoaded = null
 9          classLoader = null
10 
11          System.out.println("
開始GC"); 
12          System.gc(); 
13          System.out.println("GC
完成"); 
14        } catch (Exception e) { 
15            e.printStackTrace(); 
16        } 
17     } 
18 


        
我們增加虛擬機參數(shù)-verbosegc來觀察垃圾收集的情況,對應輸出如下:   

MyClass 
開始GC
[Full GC[Unloading 
class MyClass] 
207K->131K(1984K)
 0.0126452 secs] 
GC
完成


    
【測試場景二】使用系統(tǒng)類加載器加載,但是無法將其設置為unreachable的狀態(tài)
      
說明:將場景一中的MyClass類型字節(jié)碼文件放置到工程的輸出目錄下,以便系統(tǒng)類加載器可以加載
        

 1 public class Main { 
 2     public static void main(String[] args) { 
 3      try { 
 4       Class classLoaded =  ClassLoader.getSystemClassLoader().loadClass( 
 5 "MyClass"); 
 6 
 7 
 8      System.out.printl(sun.misc.Launcher.getLauncher().getClassLoader()); 
 9      System.out.println(classLoaded.getClassLoader()); 
10      System.out.println(Main.class.getClassLoader()); 
11 
12      classLoaded = null
13 
14      System.out.println("
開始GC"); 
15      System.gc(); 
16      System.out.println("GC
完成"); 
17 
18      //
判斷當前系統(tǒng)類加載器是否有被引用(是否是unreachable狀態(tài)
19      System.out.println(Main.class.getClassLoader()); 
20     } catch (Exception e) { 
21         e.printStackTrace(); 
22     } 
23   } 
24 

        
        
我們增加虛擬機參數(shù)-verbosegc來觀察垃圾收集的情況, 對應輸出如下: 

sun.misc.Launcher$AppClassLoader@197d257 
sun.misc.Launcher$AppClassLoader@197d257 
sun.misc.Launcher$AppClassLoader@197d257 
開始GC
[Full GC 196K->131K(1984K)
, 0.0130748 secs] 
GC
完成
sun.misc.Launcher$AppClassLoader@197d257 

        由于系統(tǒng)ClassLoader實例(AppClassLoader@197d257">sun.misc.Launcher$AppClassLoader@197d257)加載了很多類型,而且又沒有明確的接口將其設置為null,所以我們無法將加載MyClass類型的系統(tǒng)類加載器實例設置為unreachable狀態(tài), 所以通過測試結果我們可以看出,MyClass類型并沒有被卸載.(說明: 像類加載器實例這種較為特殊的對象一般在很多地方被引用,會在虛擬機中呆比較長的時間)

    
【測試場景三】使用擴展類加載器加載, 但是無法將其設置為unreachable的狀態(tài)

        說明:將測試場景二中的MyClass類型字節(jié)碼文件打包成jar放置到JRE擴展目錄下,以便擴展類加載器可以加載的到。
由于標志擴展ClassLoader實例(ExtClassLoader@7259da">sun.misc.Launcher$ExtClassLoader@7259da)加載了很多類型,而且又沒有明確的接口將其設置為null,所以我們無法將加載MyClass類型的系統(tǒng)類加載器實例設置為unreachable狀態(tài),所以通過測試結果我們可以看出,MyClass類型并沒有被卸載.
        

 1 public class Main { 
 2      public static void main(String[] args) { 
 3        try { 
 4          Class classLoaded = ClassLoader.getSystemClassLoader().getParent() 
 5 .loadClass("MyClass"); 
 6 
 7          System.out.println(classLoaded.getClassLoader()); 
 8 
 9          classLoaded = null
10 
11          System.out.println("
開始GC"); 
12          System.gc(); 
13          System.out.println("GC
完成"); 
14          //判斷當前標準擴展類加載器是否有被引用(是否是unreachable狀態(tài)
15          System.out.println(Main.class.getClassLoader().getParent()); 
16       } catch (Exception e) { 
17          e.printStackTrace(); 
18       } 
19    } 
20 

        我們增加虛擬機參數(shù)-verbosegc來觀察垃圾收集的情況,對應輸出如下:

sun.misc.Launcher$ExtClassLoader@7259da 
開始GC
[Full GC 199K->133K(1984K)
, 0.0139811 secs] 
GC
完成
sun.misc.Launcher$ExtClassLoader@7259da 


    
關于啟動類加載器我們就不需再做相關的測試了,jvm規(guī)范和JLS中已經(jīng)有明確的說明了.


    
【類型卸載總結】
    通過以上的相關測試(雖然測試的場景較為簡單)我們可以大致這樣概括:
    1
、有啟動類加載器加載的類型在整個運行期間是不可能被卸載的(jvmjls規(guī)范).
    2
、被系統(tǒng)類加載器和標準擴展類加載器加載的類型在運行期間不太可能被卸載,因為系統(tǒng)類加載器實例或者標準擴展類的實例基本上在整個運行期間總能直接或者間接的訪問的到,其達到unreachable的可能性極小.(當然,在虛擬機快退出的時候可以,因為不管ClassLoader實例或者 Class(java.lang.Class)實例也都是在堆中存在,同樣遵循垃圾收集的規(guī)則).
    3
、被開發(fā)者自定義的類加載器實例加載的類型只有在很簡單的上下文環(huán)境中才能被卸載,而且一般還要借助于強制調(diào)用虛擬機的垃圾收集功能才可以做到.可以預想,稍微復雜點的應用場景中(尤其很多時候,用戶在開發(fā)自定義類加載器實例的時候采用緩存的策略以提高系統(tǒng)性能),被加載的類型在運行期間也是 幾乎不太可能被卸載的(至少卸載的時間是不確定的).

      
綜合以上三點,我們可以默認前面的結論1,一個已經(jīng)加載的類型被卸載的幾率很小至少被卸載的時間是不確定的.同時,我們可以看的出來,開發(fā)者在開發(fā)代碼時候,不應該對虛擬機的類型卸載做任何假設的前提下來實現(xiàn)系統(tǒng)中的特定功能.

    
      
【類型更新進一步分析】
    前面已經(jīng)明確說過,被一個特定類加載器實例加載的特定類型在運行時是無法被更新的.注意這里說的
        
是一個特定的類加載器實例,而非一個特定的類加載器類型.
    
        
【測試場景四】
        
說明:現(xiàn)在要刪除前面已經(jīng)放在工程輸出目錄下和擴展目錄下的對應的MyClass類型對應的字節(jié)碼
 
        

 1 public class Main { 
 2      public static void main(String[] args) { 
 3        try { 
 4          MyURLClassLoader classLoader = new MyURLClassLoader(); 
 5          Class classLoaded1 = classLoader.loadClass("MyClass"); 
 6          Class classLoaded2 = classLoader.loadClass("MyClass"); 
 7          //
判斷兩次加載classloader實例是否相同 
 8           System.out.println(classLoaded1.getClassLoader() == classLoaded2.getClassLoader()); 
 9 
10         //
判斷兩個Class實例是否相同 
11           System.out.println(classLoaded1 == classLoaded2); 
12       } catch (Exception e) { 
13          e.printStackTrace(); 
14       } 
15    } 
16 

        輸出如下:
        true
        true

        
通過結果我們可以看出來,兩次加載獲取到的兩個Class類型實例是相同的.那是不是確實是我們的自定義
      
類加載器真正意義上加載了兩次呢(即從獲取class字節(jié)碼到定義class類型整個過程呢)?
     
通過對java.lang.ClassLoaderloadClass(String name,boolean resolve)方法進行調(diào)試,我們可以看出來,第二
     
  加載并不是真正意義上的加載,而是直接返回了上次加載的結果.

      
說明:為了調(diào)試方便, Class classLoaded2 = classLoader.loadClass("MyClass");行設置斷點,然后單步跳入,  最好能自己調(diào)試一下).L可以看到第二次加載請求返回的結果直接是上次加載的Class實例. 調(diào)試過程中的截圖
      

     
        【測試場景五】同一個類加載器實例重復加載同一類型
        說明:首先要對已有的用戶自定義類加載器做一定的修改,要覆蓋已有的類加載邏輯,MyURLClassLoader.java類簡要修改如下:重新運行測試場景四中的測試代碼
      

 1 public class MyURLClassLoader extends URLClassLoader { 
 2     //
省略部分的代碼和前面相同,只是新增如下覆蓋方法 
 3     /* 
 4     * 
覆蓋默認的加載邏輯,如果是D/classes/下的類型每次強制重新完整加載 
 5     * 
 6     * @see java.lang.ClassLoader#loadClass(java.lang.String) 
 7     */ 
 8     @Override 
 9     public Class<?> loadClass(String name) throws ClassNotFoundException { 
10      try { 
11        //
首先調(diào)用系統(tǒng)類加載器加載 
12         Class c = ClassLoader.getSystemClassLoader().loadClass(name); 
13        return c; 
14      } catch (ClassNotFoundException e) { 
15       // 
如果系統(tǒng)類加載器及其父類加載器加載不上,則調(diào)用自身邏輯來加載D/classes/下的類型 
16          return this.findClass(name); 
17      } 
18   } 
19 }

說明: this.findClass(name)會進一步調(diào)用父類URLClassLoader中的對應方法,其中涉及到了 defineClass(String name)的調(diào)用,所以說現(xiàn)在類加載器MyURLClassLoader會針對D/classes/目錄下的類型進行真正意義上的強制加載并定義對應的 類型信息.

        
測試輸出如下:
        Exception in thread"main" java.lang.LinkageError
duplicateclass definition MyClass
       atjava.lang.ClassLoader.defineClass1(Native Method)
       atjava.lang.ClassLoader.defineClass(ClassLoader.java
620)
       at java.security.SecureClassLoader.defineClass(SecureClassLoader.java
124)
       atjava.net.URLClassLoader.defineClass(URLClassLoader.java
260)
       atjava.net.URLClassLoader.access$100(URLClassLoader.java
56)
       atjava.net.URLClassLoader$1.run(URLClassLoader.java
195)
       atjava.security.AccessController.doPrivileged(Native Method)
       atjava.net.URLClassLoader.findClass(URLClassLoader.java
188)
       atMyURLClassLoader.loadClass(MyURLClassLoader.java
51)
       at Main.main(Main.java
27)
      
      
結論:如果同一個類加載器實例重復強制加載(含有定義類型defineClass動作)相同類型,會引起java.lang.LinkageError: duplicate class definition.
    
      
【測試場景六】同一個加載器類型的不同實例重復加載同一類型
       

 1 public class Main { 
 2     public static void main(String[] args) { 
 3       try { 
 4         MyURLClassLoader classLoader1 = new MyURLClassLoader(); 
 5         Class classLoaded1 = classLoader1.loadClass("MyClass"); 
 6         MyURLClassLoader classLoader2 = new MyURLClassLoader(); 
 7         Class classLoaded2 = classLoader2.loadClass("MyClass"); 
 8 
 9         //
判斷兩個Class實例是否相同 
10          System.out.println(classLoaded1 == classLoaded2); 
11       } catch (Exception e) { 
12          e.printStackTrace(); 
13       } 
14    } 
15 


     
測試對應的輸出如下:
      false
     
    
        
【類型更新總結】   
    
由不同類加載器實例重復強制加載(含有定義類型defineClass動作)同一類型不會引起java.lang.LinkageError錯誤, 但是加載結果對應的Class類型實例是不同的,即實際上是不同的類型(雖然包名+類名相同). 如果強制轉化使用,會引起ClassCastException.(說明: 頭一段時間那篇文章中解釋過,為什么不同類加載器加載同名類型實際得到的結果其實是不同類型,在JVM中一個類用其全名和一個加載類ClassLoader的實例作為唯一標識,不同類加載器加載的類將被置于不同的命名空間).


        
應用場景:我們在開發(fā)的時候可能會遇到這樣的需求,就是要動態(tài)加載某指定類型class文件的不同版本,以便能動態(tài)更新對應功能.
         
建議:
        1. 
不要寄希望于等待指定類型的以前版本被卸載,卸載行為對java開發(fā)人員透明的.
        2. 
比較可靠的做法是,每次創(chuàng)建特定類加載器的新實例來加載指定類型的不同版本,這種使用場景下,一般就要犧牲緩存特定類型的類加載器實例以帶來性能優(yōu)化的策略了.對于指定類型已經(jīng)被加載的版本, 會在適當時機達到unreachable狀態(tài),被unload并垃圾回收.每次使用完類加載器特定實例后(確定不需要再使用時), 將其顯示賦為null,這樣可能會比較快的達到jvm 規(guī)范中所說的類加載器實例unreachable狀態(tài),增大已經(jīng)不再使用的類型版本被盡快卸載的機會.
        3. 
不得不提的是,每次用新的類加載器實例去加載指定類型的指定版本,確實會帶來一定的內(nèi)存消耗,一般類加載器實例會在內(nèi)存中保留比較長的時間. bea開發(fā)者網(wǎng)站上找到一篇相關的文章(有專門分析ClassLoader的部分)http//dev2dev.bea.com/pub/a/2005/06/memory_leaks.html

           
寫的過程中參考了jvm規(guī)范和jls, 并參考了sun公司官方網(wǎng)站上的一些bug的分析文檔

          
歡迎大家批評指正!


(###)


本站僅提供存儲服務,所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
class卸載、熱替換和Tomcat的熱部署的分析
Java:JVM知識點梳理
JVM史上最最最完整深入解析!萬字長文!
JVM系列(二):JVM中類加載器相關知識筆記
JVM自定義類加載器加載指定classPath下的所有class及jar
什么是JVM?一文簡談運行機制及基本原理
更多類似文章 >>
生活服務
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服