国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

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

2019-11-11 03:06:28
字體:
來源:轉載
供稿:網友

原文:http://www.blogjava.net/heavensay/archive/2012/11/07/389685.html

這篇文章主要是分析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 name102      * @param resolve103      * @return104      * @throws ClassNotFoundException105      */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     @Override123     protected Class<?> findClass(String name) throws ClassNotFoundException {124         // TODO Auto-generated method stub125         return super.findClass(name);126     }127     128     /**129      * @param name130      * @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 name144      * @return145      */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 name157      * @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     @Override21     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      * 加載class38      */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.blogjava.net/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體系結構)
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 辰溪县| 中超| 美姑县| 庆安县| 邯郸县| 星子县| 昌图县| 东方市| 梧州市| 上虞市| 荔浦县| 定安县| 临高县| 岫岩| 巴楚县| 汉源县| 灵台县| 车致| 平乡县| 马尔康县| 蓬莱市| 屯留县| 延津县| 玉田县| 石门县| 库车县| 宣武区| 读书| 城步| 瓮安县| 新蔡县| 元朗区| 乐平市| 栖霞市| 安平县| 两当县| 合阳县| 正定县| 和顺县| 清水河县| 额尔古纳市|