<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Live a simple life

    沉默(zhu_xing@live.cn)
    隨筆 - 48, 文章 - 0, 評(píng)論 - 132, 引用 - 0
    數(shù)據(jù)加載中……

    【原創(chuàng)】Java虛擬機(jī)類型卸載和類型更新解析

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

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

        我們?cè)倏匆幌翵ava語言規(guī)范提供的關(guān)于類型卸載的更詳細(xì)的信息(部分摘錄):
        //摘自JLS 12.7 Unloading of Classes and Interfaces
        1、An implementation of the Java programming language may unload classes.
        2、Class unloading is an optimization that helps reduce memory use. Obviously,the semantics of a program should not depend  on whether and how a system chooses to implement an optimization such as class unloading.
        3、Consequently,whether a class or interface has been unloaded or not should be transparent to a program

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

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

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

        某種程度上講,在一個(gè)稍微復(fù)雜的java應(yīng)用中,我們很難準(zhǔn)確判斷出一個(gè)實(shí)例是否處于unreachable狀態(tài),所    以為了更加準(zhǔn)確的逼近這個(gè)所謂的unreachable狀態(tài),我們下面的測(cè)試代碼盡量簡(jiǎn)單一點(diǎn).
        
        【測(cè)試場(chǎng)景一】使用自定義類加載器加載, 然后測(cè)試將其設(shè)置為unreachable的狀態(tài)
        說明:
        1、自定義類加載器(為了簡(jiǎn)單起見, 這里就假設(shè)加載當(dāng)前工程以外D盤某文件夾的class)
        2、假設(shè)目前有一個(gè)簡(jiǎn)單自定義類型MyClass對(duì)應(yīng)的字節(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 

            我們?cè)黾犹摂M機(jī)參數(shù)-verbose:gc來觀察垃圾收集的情況,對(duì)應(yīng)輸出如下:   
    MyClass 
    開始GC 
    [Full GC[Unloading 
    class MyClass] 
    207K
    ->131K(1984K), 0.0126452 secs] 
    GC完成 

        【測(cè)試場(chǎng)景二】使用系統(tǒng)類加載器加載,但是無法將其設(shè)置為unreachable的狀態(tài)
          
    說明:將場(chǎng)景一中的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      //判斷當(dāng)前系統(tǒng)類加載器是否有被引用(是否是unreachable狀態(tài)) 
    19      System.out.println(Main.class.getClassLoader()); 
    20     } catch (Exception e) { 
    21         e.printStackTrace(); 
    22     } 
    23   } 
    24 
            
            我們?cè)黾犹摂M機(jī)參數(shù)-verbose:gc來觀察垃圾收集的情況, 對(duì)應(yīng)輸出如下: 
    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實(shí)例(AppClassLoader@197d257">sun.misc.Launcher$AppClassLoader@197d257)加載了很多類型,而且又沒有明確的接口將其設(shè)置為null,所以我們無法將加載MyClass類型的系統(tǒng)類加載器實(shí)例設(shè)置為unreachable狀態(tài),所以通過測(cè)試結(jié)果我們可以看出,MyClass類型并沒有被卸載.(說明: 像類加載器實(shí)例這種較為特殊的對(duì)象一般在很多地方被引用, 會(huì)在虛擬機(jī)中呆比較長(zhǎng)的時(shí)間)

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

            說明:將測(cè)試場(chǎng)景二中的MyClass類型字節(jié)碼文件打包成jar放置到JRE擴(kuò)展目錄下,以便擴(kuò)展類加載器可以加載的到。由于標(biāo)志擴(kuò)展ClassLoader實(shí)例(ExtClassLoader@7259da">sun.misc.Launcher$ExtClassLoader@7259da)加載了很多類型,而且又沒有明確的接口將其設(shè)置為null,所以我們無法將加載MyClass類型的系統(tǒng)類加載器實(shí)例設(shè)置為unreachable狀態(tài),所以通過測(cè)試結(jié)果我們可以看出,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          //判斷當(dāng)前標(biāo)準(zhǔn)擴(kuò)展類加載器是否有被引用(是否是unreachable狀態(tài)) 
    15          System.out.println(Main.class.getClassLoader().getParent()); 
    16       } catch (Exception e) { 
    17          e.printStackTrace(); 
    18       } 
    19    } 
    20 

            我們?cè)黾犹摂M機(jī)參數(shù)-verbose:gc來觀察垃圾收集的情況,對(duì)應(yīng)輸出如下:

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


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


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

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

        
          【類型更新進(jìn)一步分析】
        
    前面已經(jīng)明確說過,被一個(gè)特定類加載器實(shí)例加載的特定類型在運(yùn)行時(shí)是無法被更新的.注意這里說的
             是一個(gè)特定的類加載器實(shí)例,而非一個(gè)特定的類加載器類型.
        
            【測(cè)試場(chǎng)景四】
            說明:現(xiàn)在要?jiǎng)h除前面已經(jīng)放在工程輸出目錄下和擴(kuò)展目錄下的對(duì)應(yīng)的MyClass類型對(duì)應(yīng)的字節(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實(shí)例是否相同 
     8           System.out.println(classLoaded1.getClassLoader() == classLoaded2.getClassLoader()); 
     9 
    10         //判斷兩個(gè)Class實(shí)例是否相同 
    11           System.out.println(classLoaded1 == classLoaded2); 
    12       } catch (Exception e) { 
    13          e.printStackTrace(); 
    14       } 
    15    } 
    16 
            輸出如下:
            true
            true

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

           說明:為了調(diào)試方便, 在Class classLoaded2 = classLoader.loadClass("MyClass");行設(shè)置斷點(diǎn),然后單步跳入, 可以看到第二次加載請(qǐng)求返回的結(jié)果直接是上次加載的Class實(shí)例. 調(diào)試過程中的截圖? 最好能自己調(diào)試一下).
          
         
            【測(cè)試場(chǎng)景五】同一個(gè)類加載器實(shí)例重復(fù)加載同一類型
            說明:首先要對(duì)已有的用戶自定義類加載器做一定的修改,要覆蓋已有的類加載邏輯, MyURLClassLoader.java類簡(jiǎn)要修改如下:重新運(yùn)行測(cè)試場(chǎng)景四中的測(cè)試代碼
          
     1 public class MyURLClassLoader extends URLClassLoader { 
     2     //省略部分的代碼和前面相同,只是新增如下覆蓋方法 
     3     /* 
     4     * 覆蓋默認(rèn)的加載邏輯,如果是D:/classes/下的類型每次強(qiáng)制重新完整加載 
     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)會(huì)進(jìn)一步調(diào)用父類URLClassLoader中的對(duì)應(yīng)方法,其中涉及到了defineClass(String name)的調(diào)用,所以說現(xiàn)在類加載器MyURLClassLoader會(huì)針對(duì)D:/classes/目錄下的類型進(jìn)行真正意義上的強(qiáng)制加載并定義對(duì)應(yīng)的類型信息.

            測(cè)試輸出如下:
            Exception in thread "main" java.lang.LinkageError: duplicate class definition: MyClass
           at java.lang.ClassLoader.defineClass1(Native Method)
           at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
           at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)
           at java.net.URLClassLoader.defineClass(URLClassLoader.java:260)
           at java.net.URLClassLoader.access$100(URLClassLoader.java:56)
           at java.net.URLClassLoader$1.run(URLClassLoader.java:195)
           at java.security.AccessController.doPrivileged(Native Method)
           at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
           at MyURLClassLoader.loadClass(MyURLClassLoader.java:51)
           at Main.main(Main.java:27)
          
           結(jié)論:如果同一個(gè)類加載器實(shí)例重復(fù)強(qiáng)制加載(含有定義類型defineClass動(dòng)作)相同類型,會(huì)引起java.lang.LinkageError: duplicate class definition.
        
           【測(cè)試場(chǎng)景六】同一個(gè)加載器類型的不同實(shí)例重復(fù)加載同一類型
           
     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         //判斷兩個(gè)Class實(shí)例是否相同 
    10          System.out.println(classLoaded1 == classLoaded2); 
    11       } catch (Exception e) { 
    12          e.printStackTrace(); 
    13       } 
    14    } 
    15 

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


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

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

               歡迎大家批評(píng)指正!


    本博客中的所有文章、隨筆除了標(biāo)題中含有引用或者轉(zhuǎn)載字樣的,其他均為原創(chuàng)。轉(zhuǎn)載請(qǐng)注明出處,謝謝!

    posted on 2008-07-24 21:26 zhuxing 閱讀(6876) 評(píng)論(1)  編輯  收藏 所屬分類: Java

    評(píng)論

    # 好文啊[未登錄]  回復(fù)  更多評(píng)論   

    好文章啊!怎么沒人評(píng)論?贊一個(gè)!
    我現(xiàn)在做的一個(gè)項(xiàng)目就需要這方面的知識(shí),太感謝作者了!
    描述一下我的項(xiàng)目吧:由于業(yè)務(wù)需要,要求在輕量級(jí)J2EE架構(gòu)中(使用spring,struts2,ibatis搭建),可以動(dòng)態(tài)更新action類。我已經(jīng)試驗(yàn)了很多種方法了,比如說用Java Rebel,這個(gè)東東在開發(fā)環(huán)境用用還可以,但是不可能用于生產(chǎn)環(huán)境,pass掉。后來查了很多資料(這方面資料很少...),才發(fā)現(xiàn)可以使用自定義類加載器實(shí)現(xiàn)。目前的思路是對(duì)每一組相關(guān)action類(一組action類滿足原子性,要加載/卸載就一同加載/卸載)用一個(gè)類加載器。作者對(duì)我這個(gè)思路能否指點(diǎn)一下?我現(xiàn)在考慮的是內(nèi)存使用問題(多個(gè)類加載器耗費(fèi)更多的資源),還有其他什么問題需要考慮嗎,請(qǐng)作者幫我開拓一下思路,非常感謝:)
    2010-07-08 11:01 | tomjamescn
    主站蜘蛛池模板: 日韩a级毛片免费观看| 亚洲视频无码高清在线| 丁香六月婷婷精品免费观看| 国产精品二区三区免费播放心| 亚洲第一区视频在线观看| 久久久久成人片免费观看蜜芽| 中文字幕精品亚洲无线码一区应用| 狠狠入ady亚洲精品| 又大又硬又爽免费视频| 美女尿口扒开图片免费| 四虎永久免费地址在线网站| 老司机午夜性生免费福利| 免费国内精品久久久久影院| 免费无码国产V片在线观看| 国产福利电影一区二区三区,亚洲国模精品一区 | 国内精品99亚洲免费高清| 一日本道a高清免费播放| 精品久久久久久亚洲| 国产精品99精品久久免费| 亚洲精品综合久久中文字幕 | 东方aⅴ免费观看久久av| 亚洲av日韩av无码| 日韩免费一区二区三区在线播放| 激情五月亚洲色图| 国产成人免费网站在线观看 | 高清免费久久午夜精品| 亚洲成色WWW久久网站| 91网站免费观看| 色综合久久精品亚洲国产| 亚洲综合伊人久久大杳蕉| 久久久久久夜精品精品免费啦| 91亚洲性爱在线视频| 免费一看一级毛片全播放| 日本三级在线观看免费| 亚洲不卡在线观看| 亚洲AV中文无码乱人伦在线视色| 另类免费视频一区二区在线观看 | 一级特黄录像免费播放中文版| 亚洲国产精品无码成人片久久| 可以免费看黄的网站| 一级做a爰性色毛片免费|