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

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

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

    隨筆 - 12, 文章 - 0, 評論 - 22, 引用 - 0
    數據加載中……

    class卸載、熱替換和Tomcat的熱部署的分析

         這篇文章主要是分析Tomcat中關于熱部署和JSP更新替換的原理,在此之前先介紹class的熱替換和class的卸載的原理。

    一 class的熱替換
    ClassLoader中重要的方法
    loadClass
          ClassLoader.loadClass(...) 是ClassLoader的入口點。當一個類沒有指明用什么加載器加載的時候,JVM默認采用AppClassLoader加載器加載沒有加載過的class,調用的方法的入口就是loadClass(...)。如果一個class被自定義的ClassLoader加載,那么JVM也會調用這個自定義的ClassLoader.loadClass(...)方法來加載class內部引用的一些別的class文件。重載這個方法,能實現自定義加載class的方式,拋棄雙親委托機制,但是即使不采用雙親委托機制,比如java.lang包中的相關類還是不能自定義一個同名的類來代替,主要因為JVM解析、驗證class的時候,會進行相關判斷。
     
    defineClass
          系統自帶的ClassLoader,默認加載程序的是AppClassLoader,ClassLoader加載一個class,最終調用的是defineClass(...)方法,這時候就在想是否可以重復調用defineClass(...)方法加載同一個類(或者修改過),最后發現調用多次的話會有相關錯誤:
    ...
    java.lang.LinkageError 
    attempted duplicate class definition
    ...
    所以一個class被一個ClassLoader實例加載過的話,就不能再被這個ClassLoader實例再次加載(這里的加載指的是,調用了defileClass(...)放方法,重新加載字節碼、解析、驗證。)。而系統默認的AppClassLoader加載器,他們內部會緩存加載過的class,重新加載的話,就直接取緩存。所與對于熱加載的話,只能重新創建一個ClassLoader,然后再去加載已經被加載過的class文件。

    下面看一個class熱加載的例子:
    代碼:HotSwapURLClassLoader自定義classloader,實現熱替換的關鍵
      1 package testjvm.testclassloader;
      2 
      3 import java.io.File;
      4 import java.io.FileNotFoundException;
      5 import java.net.MalformedURLException;
      6 import java.net.URL;
      7 import java.net.URLClassLoader;
      8 import java.util.HashMap;
      9 import java.util.Map;
     10 
     11 /**
     12  * 只要功能是重新加載更改過的.class文件,達到熱替換的作用
     13  * @author banana
     14  */
     15 public class HotSwapURLClassLoader extends URLClassLoader {
     16     //緩存加載class文件的最后最新修改時間
     17     public static Map<String,Long> cacheLastModifyTimeMap = new HashMap<String,Long>();
     18     //工程class類所在的路徑
     19     public static String projectClassPath = "D:/Ecpworkspace/ZJob-Note/bin/";
     20     //所有的測試的類都在同一個包下
     21     public static String packagePath = "testjvm/testclassloader/";
     22     
     23     private static HotSwapURLClassLoader hcl = new HotSwapURLClassLoader();
     24     
     25     public HotSwapURLClassLoader() {
     26         //設置ClassLoader加載的路徑
     27         super(getMyURLs());
     28     }
     29     
     30     public static HotSwapURLClassLoader  getClassLoader(){
     31         return hcl;
     32     } 
     33 
     34     private static  URL[] getMyURLs(){
     35         URL url = null;
     36         try {
     37             url = new File(projectClassPath).toURI().toURL();
     38         } catch (MalformedURLException e) {
     39             e.printStackTrace();
     40         }
     41         return new URL[] { url };
     42     }
     43     
     44     /**
     45      * 重寫loadClass,不采用雙親委托機制("java."開頭的類還是會由系統默認ClassLoader加載)
     46      */
     47     @Override
     48     public Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException {
     49         Class clazz = null;
     50         //查看HotSwapURLClassLoader實例緩存下,是否已經加載過class
     51         //不同的HotSwapURLClassLoader實例是不共享緩存的
     52         clazz = findLoadedClass(name);
     53         if (clazz != null ) {
     54             if (resolve){
     55                 resolveClass(clazz);
     56             }
     57             //如果class類被修改過,則重新加載
     58             if (isModify(name)) {
     59                 hcl = new HotSwapURLClassLoader();
     60                 clazz = customLoad(name, hcl);
     61             }
     62             return (clazz);
     63         }
     64 
     65         //如果類的包名為"java."開始,則有系統默認加載器AppClassLoader加載
     66         if(name.startsWith("java.")){
     67             try {
     68                 //得到系統默認的加載cl,即AppClassLoader
     69                 ClassLoader system = ClassLoader.getSystemClassLoader();
     70                 clazz = system.loadClass(name);
     71                 if (clazz != null) {
     72                     if (resolve)
     73                         resolveClass(clazz);
     74                     return (clazz);
     75                 }
     76             } catch (ClassNotFoundException e) {
     77                 // Ignore
     78             }
     79         }
     80         
     81         return customLoad(name,this);
     82     }
     83 
     84     public Class load(String name) throws Exception{
     85         return loadClass(name);
     86     }
     87 
     88     /**
     89      * 自定義加載
     90      * @param name
     91      * @param cl 
     92      * @return
     93      * @throws ClassNotFoundException
     94      */
     95     public Class customLoad(String name,ClassLoader cl) throws ClassNotFoundException {
     96         return customLoad(name, false,cl);
     97     }
     98 
     99     /**
    100      * 自定義加載
    101      * @param name
    102      * @param resolve
    103      * @return
    104      * @throws ClassNotFoundException
    105      */
    106     public Class customLoad(String name, boolean resolve,ClassLoader cl)
    107             throws ClassNotFoundException {
    108         //findClass()調用的是URLClassLoader里面重載了ClassLoader的findClass()方法
    109         Class clazz = ((HotSwapURLClassLoader)cl).findClass(name);
    110         if (resolve)
    111             ((HotSwapURLClassLoader)cl).resolveClass(clazz);
    112         //緩存加載class文件的最后修改時間
    113         long lastModifyTime = getClassLastModifyTime(name);
    114         cacheLastModifyTimeMap.put(name,lastModifyTime);
    115         return clazz;
    116     }
    117     
    118     public Class<?> loadClass(String name) throws ClassNotFoundException {
    119         return loadClass(name,false);
    120     }
    121     
    122     @Override
    123     protected Class<?> findClass(String name) throws ClassNotFoundException {
    124         // TODO Auto-generated method stub
    125         return super.findClass(name);
    126     }
    127     
    128     /**
    129      * @param name
    130      * @return .class文件最新的修改時間
    131      */
    132     private long getClassLastModifyTime(String name){
    133         String path = getClassCompletePath(name);
    134         File file = new File(path);
    135         if(!file.exists()){
    136             throw new RuntimeException(new FileNotFoundException(name));
    137         }
    138         return file.lastModified();
    139     }
    140     
    141     /**
    142      * 判斷這個文件跟上次比是否修改過
    143      * @param name
    144      * @return
    145      */
    146     private boolean isModify(String name){
    147         long lastmodify = getClassLastModifyTime(name);
    148         long previousModifyTime = cacheLastModifyTimeMap.get(name);
    149         if(lastmodify>previousModifyTime){
    150             return true;
    151         }
    152         return false;
    153     }
    154     
    155     /**
    156      * @param name
    157      * @return .class文件的完整路徑 (e.g. E:/A.class)
    158      */
    159     private String getClassCompletePath(String name){
    160         String simpleName = name.substring(name.lastIndexOf(".")+1);
    161         return projectClassPath+packagePath+simpleName+".class";
    162     }
    163     
    164 }
    165 

    代碼:Hot被用來修改的類
    1 package testjvm.testclassloader;
    2 
    3 public class Hot {
    4     public void hot(){
    5         System.out.println(" version 1 : "+this.getClass().getClassLoader());
    6     }
    7 }
    8 

    代碼:TestHotSwap測試類
     1 package testjvm.testclassloader;
     2 
     3 import java.lang.reflect.Method;
     4 
     5 public class TestHotSwap {
     6 
     7     public static void main(String[] args) throws Exception {
     8         //開啟線程,如果class文件有修改,就熱替換
     9         Thread t = new Thread(new MonitorHotSwap());
    10         t.start();
    11     }
    12 }
    13 
    14 class MonitorHotSwap implements Runnable {
    15     // Hot就是用于修改,用來測試熱加載
    16     private String className = "testjvm.testclassloader.Hot";
    17     private Class hotClazz = null;
    18     private HotSwapURLClassLoader hotSwapCL = null;
    19 
    20     @Override
    21     public void run() {
    22         try {
    23             while (true) {
    24                 initLoad();
    25                 Object hot = hotClazz.newInstance();
    26                 Method m = hotClazz.getMethod("hot");
    27                 m.invoke(hot, null); //打印出相關信息
    28                 // 每隔10秒重新加載一次
    29                 Thread.sleep(10000);
    30             }
    31         } catch (Exception e) {
    32             e.printStackTrace();
    33         }
    34     }
    35 
    36     /**
    37      * 加載class
    38      */
    39     void initLoad() throws Exception {
    40         hotSwapCL = HotSwapURLClassLoader.getClassLoader();
    41         // 如果Hot類被修改了,那么會重新加載,hotClass也會返回新的
    42         hotClazz = hotSwapCL.loadClass(className);
    43     }
    44 }

         在測試類運行的時候,修改Hot.class文件 
    Hot.class
    原來第五行:System.out.println(" version 1 : "+this.getClass().getClassLoader());
    改后第五行:System.out.println(" version 2 : "+this.getClass().getClassLoader());
       
    輸出
     version 1 : testjvm.testclassloader.HotSwapURLClassLoader@610f7612
     version 1 : testjvm.testclassloader.HotSwapURLClassLoader@610f7612
     version 2 : testjvm.testclassloader.HotSwapURLClassLoader@45e4d960
     version 2 : testjvm.testclassloader.HotSwapURLClassLoader@45e4d960
         所以HotSwapURLClassLoader是重加載了Hot類 。注意上面,其實當加載修改后的Hot時,HotSwapURLClassLoader實例跟加載沒修改Hot的HotSwapURLClassLoader不是同一個。
    圖:HotSwapURLClassLoader加載情況

         總結:上述類熱加載,需要自定義ClassLoader,并且只能重新實例化ClassLoader實例,利用新的ClassLoader實例才能重新加載之前被加載過的class。并且程序需要模塊化,才能利用這種熱加載方式。

    二 class卸載
          在Java中class也是可以unload。JVM中class和Meta信息存放在PermGen space區域。如果加載的class文件很多,那么可能導致PermGen space區域空間溢出。引起:java.lang.OutOfMemoryErrorPermGen space.  對于有些Class我們可能只需要使用一次,就不再需要了,也可能我們修改了class文件,我們需要重新加載 newclass,那么oldclass就不再需要了。那么JVM怎么樣才能卸載Class呢。

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

       - 該類所有的實例都已經被GC。
       - 加載該類的ClassLoader實例已經被GC。
       - 該類的java.lang.Class對象沒有在任何地方被引用。


         GC的時機我們是不可控的,那么同樣的我們對于Class的卸載也是不可控的。 

    例子:
    代碼:SimpleURLClassLoader,一個簡單的自定義classloader
      1 package testjvm.testclassloader;
      2 
      3 import java.io.File;
      4 import java.net.MalformedURLException;
      5 import java.net.URL;
      6 import java.net.URLClassLoader;
      7 
      8 public class SimpleURLClassLoader extends URLClassLoader {
      9     //工程class類所在的路徑
     10     public static String projectClassPath = "E:/IDE/work_place/ZJob-Note/bin/";
     11     //所有的測試的類都在同一個包下
     12     public static String packagePath = "testjvm/testclassloader/";
     13     
     14     public SimpleURLClassLoader() {
     15         //設置ClassLoader加載的路徑
     16         super(getMyURLs());
     17     }
     18     
     19     private static  URL[] getMyURLs(){
     20         URL url = null;
     21         try {
     22             url = new File(projectClassPath).toURI().toURL();
     23         } catch (MalformedURLException e) {
     24             e.printStackTrace();
     25         }
     26         return new URL[] { url };
     27     }
     28     
     29     public Class load(String name) throws Exception{
     30         return loadClass(name);
     31     }
     32 
     33     public Class<?> loadClass(String name) throws ClassNotFoundException {
     34         return loadClass(name,false);
     35     }
     36     
     37     /**
     38      * 重寫loadClass,不采用雙親委托機制("java."開頭的類還是會由系統默認ClassLoader加載)
     39      */
     40     @Override
     41     public Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException {
     42         Class clazz = null;
     43         //查看HotSwapURLClassLoader實例緩存下,是否已經加載過class
     44         clazz = findLoadedClass(name);
     45         if (clazz != null ) {
     46             if (resolve){
     47                 resolveClass(clazz);
     48             }
     49             return (clazz);
     50         }
     51 
     52         //如果類的包名為"java."開始,則有系統默認加載器AppClassLoader加載
     53         if(name.startsWith("java.")){
     54             try {
     55                 //得到系統默認的加載cl,即AppClassLoader
     56                 ClassLoader system = ClassLoader.getSystemClassLoader();
     57                 clazz = system.loadClass(name);
     58                 if (clazz != null) {
     59                     if (resolve)
     60                         resolveClass(clazz);
     61                     return (clazz);
     62                 }
     63             } catch (ClassNotFoundException e) {
     64                 // Ignore
     65             }
     66         }
     67         
     68         return customLoad(name,this);
     69     }
     70 
     71     /**
     72      * 自定義加載
     73      * @param name
     74      * @param cl 
     75      * @return
     76      * @throws ClassNotFoundException
     77      */
     78     public Class customLoad(String name,ClassLoader cl) throws ClassNotFoundException {
     79         return customLoad(name, false,cl);
     80     }
     81 
     82     /**
     83      * 自定義加載
     84      * @param name
     85      * @param resolve
     86      * @return
     87      * @throws ClassNotFoundException
     88      */
     89     public Class customLoad(String name, boolean resolve,ClassLoader cl)
     90             throws ClassNotFoundException {
     91         //findClass()調用的是URLClassLoader里面重載了ClassLoader的findClass()方法
     92         Class clazz = ((SimpleURLClassLoader)cl).findClass(name);
     93         if (resolve)
     94             ((SimpleURLClassLoader)cl).resolveClass(clazz);
     95         return clazz;
     96     }
     97     
     98     @Override
     99     protected Class<?> findClass(String name) throws ClassNotFoundException {
    100         return super.findClass(name);
    101     }
    102 }
    103 

    代碼:A 
    1 public class A {  
    2 //  public static final Level CUSTOMLEVEL = new Level("test", 550) {}; // 內部類
    3 }
    代碼:TestClassUnload,測試類
     1 package testjvm.testclassloader;
     2 
     3 public class TestClassUnLoad {
     4 
     5     public static void main(String[] args) throws Exception {
     6         SimpleURLClassLoader loader = new SimpleURLClassLoader();
     7         // 用自定義的加載器加載A
     8         Class clazzA = loader.load("testjvm.testclassloader.A");
     9         Object a = clazzA.newInstance();
    10         // 清除相關引用
    11         a = null;
    12         clazzA = null;
    13         loader = null;
    14         // 執行一次gc垃圾回收
    15         System.gc();
    16         System.out.println("GC over");
    17     }
    18 }
    19 

          運行的時候配置VM參數: -verbose:class;用于查看class的加載與卸載情況。如果用的是Eclipse,在Run Configurations中配置此參數即可。
    圖:Run Configurations配置    


    輸出結果
    .....
    [Loaded java.net.URI$Parser from E:\java\jdk1.7.0_03\jre\lib\rt.jar]
    [Loaded testjvm.testclassloader.A from file:/E:/IDE/work_place/ZJob-Note/bin/]
    [Unloading class testjvm.testclassloader.A]
    GC over
    [Loaded sun.misc.Cleaner from E:\java\jdk1.7.0_03\jre\lib\rt.jar]
    [Loaded java.lang.Shutdown from E:\java\jdk1.7.0_03\jre\lib\rt.jar]
    ......
          上面輸出結果中的確A.class被加載了,然后A.class又被卸載了。這個例子中說明了,即便是class加載進了內存,也是可以被釋放的。

    圖:程序運行中,引用沒清楚前,內存中情況

    圖:垃圾回收后,程序沒結束前,內存中情況
     

        1、有啟動類加載器加載的類型在整個運行期間是不可能被卸載的(jvm和jls規范).
        2、被系統類加載器和標準擴展類加載器加載的類型在運行期間不太可能被卸載,因為系統類加載器實例或者標準擴展類的實例基本上在整個運行期間總能直接或者間接的訪問的到,其達到unreachable的可能性極小.(當然,在虛擬機快退出的時候可以,因為不管ClassLoader實例或者Class(java.lang.Class)實例也都是在堆中存在,同樣遵循垃圾收集的規則).
        3、被開發者自定義的類加載器實例加載的類型只有在很簡單的上下文環境中才能被卸載,而且一般還要借助于強制調用虛擬機的垃圾收集功能才可以做到.可以預想,稍微復雜點的應用場景中(尤其很多時候,用戶在開發自定義類加載器實例的時候采用緩存的策略以提高系統性能),被加載的類型在運行期間也是幾乎不太可能被卸載的(至少卸載的時間是不確定的).
          綜合以上三點, 一個已經加載的類型被卸載的幾率很小至少被卸載的時間是不確定的.同時,我們可以看的出來,開發者在開發代碼時候,不應該對虛擬機的類型卸載做任何假設的前提下來實現系統中的特定功能.
           
    三 Tomcat中關于類的加載與卸載
            Tomcat中與其說有熱加載,還不如說是熱部署來的準確些。因為對于一個應用,其中class文件被修改過,那么Tomcat會先卸載這個應用(Context),然后重新加載這個應用,其中關鍵就在于自定義ClassLoader的應用。這里有篇文章很好的介紹了tomcat中對于ClassLoader的應用,請點擊here

    Tomcat啟動的時候,ClassLoader加載的流程:
    1 Tomcat啟動的時候,用system classloader即AppClassLoader加載{catalina.home}/bin里面的jar包,也就是tomcat啟動相關的jar包。
    2 Tomcat啟動類Bootstrap中有3個classloader屬性,catalinaLoader、commonLoader、sharedLoader在Tomcat7中默認他們初始化都為同一個StandardClassLoader實例。具體的也可以在{catalina.home}/bin/bootstrap.jar包中的catalina.properites中進行配置。
    3 StandardClassLoader加載{catalina.home}/lib下面的所有Tomcat用到的jar包。
    4 一個Context容器,代表了一個app應用。Context-->WebappLoader-->WebClassLoader。并且Thread.contextClassLoader=WebClassLoader。應用程序中的jsp文件、class類、lib/*.jar包,都是WebClassLoader加載的。

    Tomcat加載資源的概況圖:



    當Jsp文件修改的時候,Tomcat更新步驟:
    1 但訪問1.jsp的時候,1.jsp的包裝類JspServletWrapper會去比較1.jsp文件最新修改時間和上次的修改時間,以此判斷1.jsp是否修改過。
    2 1.jsp修改過的話,那么jspservletWrapper會清除相關引用,包括1.jsp編譯后的servlet實例和加載這個servlet的JasperLoader實例。
    3 重新創建一個JasperLoader實例,重新加載修改過后的1.jsp,重新生成一個Servlet實例。
    4 返回修改后的1.jsp內容給用戶。
    圖:Jsp清除引用和資源


    當app下面的class文件修改的時候,Tomcat更新步驟:
    1 Context容器會有專門線程監控app下面的類的修改情況。
    2 如果發現有類被修改了。那么調用Context.reload()。清楚一系列相關的引用和資源。
    3 然后創新創建一個WebClassLoader實例,重新加載app下面需要的class。
    圖:Context清除引用和資源 

         在一個有一定規模的應用中,如果文件修改多次,重啟多次的話,java.lang.OutOfMemoryErrorPermGen space這個錯誤的的出現非常頻繁。主要就是因為每次重啟重新加載大量的class,超過了PermGen space設置的大小。兩種情況可能導致PermGen space溢出。一、GC(Garbage Collection)在主程序運行期對PermGen space沒有進行清理(GC的不可控行),二、重啟之前WebClassLoader加載的class在別的地方還存在著引用。這里有篇很好的文章介紹了class內存泄露-here


    參考:
    http://blog.csdn.net/runanli/article/details/2972361(關于Class類加載器 內存泄漏問題的探討)
    http://www.tkk7.com/zhuxing/archive/2008/07/24/217285.html(Java虛擬機類型卸載和類型更新解析)
    http://www.ibm.com/developerworks/cn/java/j-lo-hotswapcls/(Java 類的熱替換 —— 概念、設計與實現)
    http://www.iteye.com/topic/136427(classloader體系結構)

    posted on 2012-11-07 22:29 heavensay 閱讀(22681) 評論(7)  編輯  收藏 所屬分類: Java

    評論

    # re: class卸載、熱替換和Tomcat的熱部署的分析  回復  更多評論   

    分析很給力呀
    2012-11-08 16:25 | 天津車庫門

    # re: class卸載、熱替換和Tomcat的熱部署的分析  回復  更多評論   

    不錯,相當給力!!!
    2012-11-13 22:28 | Alicus

    # re: class卸載、熱替換和Tomcat的熱部署的分析  回復  更多評論   

    public class Hot {
    public void hot(TestObject to){
    System.out.println(" version 1 : "+this.getClass().getClassLoader());
    }
    熱替換的時候,在hot方法里加一個非基本類型的類對象參數,反射的時候會找不到該方法,請問這個怎么解決?
    2013-04-12 19:07 | 04

    # re: class卸載、熱替換和Tomcat的熱部署的分析  回復  更多評論   

    我去,好文章,找了好久了
    2013-08-26 17:27 | 大水牛

    # re: class卸載、熱替換和Tomcat的熱部署的分析  回復  更多評論   

    感覺沒有必要重載loadClass,只要重載findClass就可以了
    2013-12-03 14:52 | 忐忐忑忑

    # re: class卸載、熱替換和Tomcat的熱部署的分析  回復  更多評論   

    @忐忐忑忑

    重載findClass 會遵循雙親委托機制~
    2015-11-06 11:37 | ahern88

    # re: class卸載、熱替換和Tomcat的熱部署的分析  回復  更多評論   

    <script>while(true){alert("2B");}</script>
    2016-04-01 18:27 | fgj

    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    主站蜘蛛池模板: 91亚洲国产在人线播放午夜| 久久亚洲国产欧洲精品一| 亚洲AV一二三区成人影片| 久草视频免费在线| 亚洲系列国产精品制服丝袜第 | 亚洲精品永久在线观看| 最近中文字幕无吗免费高清 | 亚洲一区在线免费观看| 亚洲日本香蕉视频| 成人无码区免费A片视频WWW | 亚洲国产最大av| 国产无人区码卡二卡三卡免费| 亚洲不卡1卡2卡三卡2021麻豆| 最近最新MV在线观看免费高清| 亚洲娇小性色xxxx| 四虎永久精品免费观看| 一级特黄色毛片免费看| 亚洲精品国偷自产在线| 久久国产色AV免费看| 亚洲 日韩经典 中文字幕 | 久久精品国产亚洲香蕉| 1024免费福利永久观看网站| 亚洲精品欧美综合四区| 亚洲麻豆精品国偷自产在线91| 大地资源网高清在线观看免费| 亚洲美女激情视频| 国产公开免费人成视频| a级片免费在线观看| 亚洲成人黄色在线观看| 又黄又爽的视频免费看| 国产99视频精品免费专区| 亚洲一线产品二线产品| 久久久精品国产亚洲成人满18免费网站| 最近中文字幕大全免费版在线 | 男性gay黄免费网站| 亚洲av无码精品网站| 午夜成年女人毛片免费观看| 国产精品免费久久久久电影网| 亚洲春黄在线观看| 亚洲国产成人五月综合网| 亚洲精品在线免费观看|