原文:http://www.blogjava.net/heavensay/archive/2012/11/07/389685.html
這篇文章主要是分析Tomcat中關(guān)于熱部署和jsp更新替換的原理,在此之前先介紹class的熱替換和class的卸載的原理。
一 class的熱替換ClassLoader中重要的方法loadClass ClassLoader.loadClass(...) 是ClassLoader的入口點(diǎn)。當(dāng)一個(gè)類沒(méi)有指明用什么加載器加載的時(shí)候,JVM默認(rèn)采用AppClassLoader加載器加載沒(méi)有加載過(guò)的class,調(diào)用的方法的入口就是loadClass(...)。如果一個(gè)class被自定義的ClassLoader加載,那么JVM也會(huì)調(diào)用這個(gè)自定義的ClassLoader.loadClass(...)方法來(lái)加載class內(nèi)部引用的一些別的class文件。重載這個(gè)方法,能實(shí)現(xiàn)自定義加載class的方式,拋棄雙親委托機(jī)制,但是即使不采用雙親委托機(jī)制,比如java.lang包中的相關(guān)類還是不能自定義一個(gè)同名的類來(lái)代替,主要因?yàn)镴VM解析、驗(yàn)證class的時(shí)候,會(huì)進(jìn)行相關(guān)判斷。 defineClass 系統(tǒng)自帶的ClassLoader,默認(rèn)加載程序的是AppClassLoader,ClassLoader加載一個(gè)class,最終調(diào)用的是defineClass(...)方法,這時(shí)候就在想是否可以重復(fù)調(diào)用defineClass(...)方法加載同一個(gè)類(或者修改過(guò)),最后發(fā)現(xiàn)調(diào)用多次的話會(huì)有相關(guān)錯(cuò)誤:...java.lang.LinkageError attempted duplicate class definition...所以一個(gè)class被一個(gè)ClassLoader實(shí)例加載過(guò)的話,就不能再被這個(gè)ClassLoader實(shí)例再次加載(這里的加載指的是,調(diào)用了defileClass(...)放方法,重新加載字節(jié)碼、解析、驗(yàn)證。)。而系統(tǒng)默認(rèn)的AppClassLoader加載器,他們內(nèi)部會(huì)緩存加載過(guò)的class,重新加載的話,就直接取緩存。所與對(duì)于熱加載的話,只能重新創(chuàng)建一個(gè)ClassLoader,然后再去加載已經(jīng)被加載過(guò)的class文件。下面看一個(gè)class熱加載的例子:代碼:HotSwapURLClassLoader自定義classloader,實(shí)現(xiàn)熱替換的關(guān)鍵 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 * 只要功能是重新加載更改過(guò)的.class文件,達(dá)到熱替換的作用 13 * @author banana 14 */ 15 public class HotSwapURLClassLoader extends URLClassLoader { 16 //緩存加載class文件的最后最新修改時(shí)間 17 public static Map<String,Long> cacheLastModifyTimeMap = new HashMap<String,Long>(); 18 //工程class類所在的路徑 19 public static String PRojectClassPath = "D:/Ecpworkspace/ZJob-Note/bin/"; 20 //所有的測(cè)試的類都在同一個(gè)包下 21 public static String packagePath = "testjvm/testclassloader/"; 22 23 private static HotSwapURLClassLoader hcl = new HotSwapURLClassLoader(); 24 25 public HotSwapURLClassLoader() { 26 //設(shè)置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,不采用雙親委托機(jī)制("java."開(kāi)頭的類還是會(huì)由系統(tǒng)默認(rèn)ClassLoader加載) 46 */ 47 @Override 48 public Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException { 49 Class clazz = null; 50 //查看HotSwapURLClassLoader實(shí)例緩存下,是否已經(jīng)加載過(guò)class 51 //不同的HotSwapURLClassLoader實(shí)例是不共享緩存的 52 clazz = findLoadedClass(name); 53 if (clazz != null ) { 54 if (resolve){ 55 resolveClass(clazz); 56 } 57 //如果class類被修改過(guò),則重新加載 58 if (isModify(name)) { 59 hcl = new HotSwapURLClassLoader(); 60 clazz = customLoad(name, hcl); 61 } 62 return (clazz); 63 } 64 65 //如果類的包名為"java."開(kāi)始,則有系統(tǒng)默認(rèn)加載器AppClassLoader加載 66 if(name.startsWith("java.")){ 67 try { 68 //得到系統(tǒng)默認(rèn)的加載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(
)調(diào)用的是URLClassLoader里面重載了ClassLoader的findClass(
)方法109 Class clazz = ((HotSwapURLClassLoader)cl).findClass(name);110 if (resolve)111 ((HotSwapURLClassLoader)cl).resolveClass(clazz);112 //緩存加載class文件的最后修改時(shí)間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文件最新的修改時(shí)間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 * 判斷這個(gè)文件跟上次比是否修改過(guò)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被用來(lái)修改的類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測(cè)試類 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 //開(kāi)啟線程,如果class文件有修改,就熱替換 9 Thread t = new Thread(new MonitorHotSwap());10 t.start();11 }12 }13 14 class MonitorHotSwap implements Runnable {15 // Hot就是用于修改,用來(lái)測(cè)試熱加載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); //打印出相關(guān)信息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類被修改了,那么會(huì)重新加載,hotClass也會(huì)返回新的42 hotClazz = hotSwapCL.loadClass(className);43 }44 } 在測(cè)試類運(yùn)行的時(shí)候,修改Hot.class文件 所以HotSwapURLClassLoader是重加載了Hot類 。注意上面,其實(shí)當(dāng)加載修改后的Hot時(shí),HotSwapURLClassLoader實(shí)例跟加載沒(méi)修改Hot的HotSwapURLClassLoader不是同一個(gè)。圖:HotSwapURLClassLoader加載情況
總結(jié):上述類熱加載,需要自定義ClassLoader,并且只能重新實(shí)例化ClassLoader實(shí)例,利用新的ClassLoader實(shí)例才能重新加載之前被加載過(guò)的class。并且程序需要模塊化,才能利用這種熱加載方式。二 class卸載 在Java中class也是可以u(píng)nload。JVM中class和Meta信息存放在PermGen space區(qū)域。如果加載的class文件很多,那么可能導(dǎo)致PermGen space區(qū)域空間溢出。引起:java.lang.OutOfMemoryErrorPermGen space. 對(duì)于有些Class我們可能只需要使用一次,就不再需要了,也可能我們修改了class文件,我們需要重新加載 newclass,那么oldclass就不再需要了。那么JVM怎么樣才能卸載Class呢。 JVM中的Class只有滿足以下三個(gè)條件,才能被GC回收,也就是該Class被卸載(unload):- 該類所有的實(shí)例都已經(jīng)被GC。 - 加載該類的ClassLoader實(shí)例已經(jīng)被GC。 - 該類的java.lang.Class對(duì)象沒(méi)有在任何地方被引用。
GC的時(shí)機(jī)我們是不可控的,那么同樣的我們對(duì)于Class的卸載也是不可控的。 例子:代碼:SimpleURLClassLoader,一個(gè)簡(jiǎn)單的自定義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 //所有的測(cè)試的類都在同一個(gè)包下 12 public static String packagePath = "testjvm/testclassloader/"; 13 14 public SimpleURLClassLoader() { 15 //設(shè)置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,不采用雙親委托機(jī)制("java."開(kāi)頭的類還是會(huì)由系統(tǒng)默認(rèn)ClassLoader加載) 39 */ 40 @Override 41 public Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException { 42 Class clazz = null; 43 //查看HotSwapURLClassLoader實(shí)例緩存下,是否已經(jīng)加載過(guò)class 44 clazz = findLoadedClass(name); 45 if (clazz != null ) { 46 if (resolve){ 47 resolveClass(clazz); 48 } 49 return (clazz); 50 } 51 52 //如果類的包名為"java."開(kāi)始,則有系統(tǒng)默認(rèn)加載器AppClassLoader加載 53 if(name.startsWith("java.")){ 54 try { 55 //得到系統(tǒng)默認(rèn)的加載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(
)調(diào)用的是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) {}; // 內(nèi)部類3 }代碼:TestClassUnload,測(cè)試類 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 // 清除相關(guān)引用11 a = null;12 clazzA = null;13 loader = null;14 // 執(zhí)行一次gc垃圾回收15 System.gc();16 System.out.println("GC over");17 }18 }19 運(yùn)行的時(shí)候配置VM參數(shù): -verbose:class;用于查看class的加載與卸載情況。如果用的是Eclipse,在Run Configurations中配置此參數(shù)即可。圖:Run Configurations配置
上面輸出結(jié)果中的確A.class被加載了,然后A.class又被卸載了。這個(gè)例子中說(shuō)明了,即便是class加載進(jìn)了內(nèi)存,也是可以被釋放的。圖:程序運(yùn)行中,引用沒(méi)清楚前,內(nèi)存中情況
圖:垃圾回收后,程序沒(méi)結(jié)束前,內(nèi)存中情況
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)行期間總能直接或者間接的訪問(wèn)的到,其達(dá)到unreachable的可能性極小.(當(dāng)然,在虛擬機(jī)快退出的時(shí)候可以,因?yàn)椴还蹸lassLoader實(shí)例或者Class(java.lang.Class)實(shí)例也都是在堆中存在,同樣遵循垃圾收集的規(guī)則). 3、被開(kāi)發(fā)者自定義的類加載器實(shí)例加載的類型只有在很簡(jiǎn)單的上下文環(huán)境中才能被卸載,而且一般還要借助于強(qiáng)制調(diào)用虛擬機(jī)的垃圾收集功能才可以做到.可以預(yù)想,稍微復(fù)雜點(diǎn)的應(yīng)用場(chǎng)景中(尤其很多時(shí)候,用戶在開(kāi)發(fā)自定義類加載器實(shí)例的時(shí)候采用緩存的策略以提高系統(tǒng)性能),被加載的類型在運(yùn)行期間也是幾乎不太可能被卸載的(至少卸載的時(shí)間是不確定的). 綜合以上三點(diǎn), 一個(gè)已經(jīng)加載的類型被卸載的幾率很小至少被卸載的時(shí)間是不確定的.同時(shí),我們可以看的出來(lái),開(kāi)發(fā)者在開(kāi)發(fā)代碼時(shí)候,不應(yīng)該對(duì)虛擬機(jī)的類型卸載做任何假設(shè)的前提下來(lái)實(shí)現(xiàn)系統(tǒng)中的特定功能. 三 Tomcat中關(guān)于類的加載與卸載 Tomcat中與其說(shuō)有熱加載,還不如說(shuō)是熱部署來(lái)的準(zhǔn)確些。因?yàn)閷?duì)于一個(gè)應(yīng)用,其中class文件被修改過(guò),那么Tomcat會(huì)先卸載這個(gè)應(yīng)用(Context),然后重新加載這個(gè)應(yīng)用,其中關(guān)鍵就在于自定義ClassLoader的應(yīng)用。這里有篇文章很好的介紹了tomcat中對(duì)于ClassLoader的應(yīng)用,請(qǐng)點(diǎn)擊here。Tomcat啟動(dòng)的時(shí)候,ClassLoader加載的流程:1 Tomcat啟動(dòng)的時(shí)候,用system classloader即AppClassLoader加載{catalina.home}/bin里面的jar包,也就是tomcat啟動(dòng)相關(guān)的jar包。2 Tomcat啟動(dòng)類Bootstrap中有3個(gè)classloader屬性,catalinaLoader、commonLoader、sharedLoader在Tomcat7中默認(rèn)他們初始化都為同一個(gè)StandardClassLoader實(shí)例。具體的也可以在{catalina.home}/bin/bootstrap.jar包中的catalina.properites中進(jìn)行配置。3 StandardClassLoader加載{catalina.home}/lib下面的所有Tomcat用到的jar包。4 一個(gè)Context容器,代表了一個(gè)app應(yīng)用。Context-->WebappLoader-->WebClassLoader。并且Thread.contextClassLoader=WebClassLoader。應(yīng)用程序中的jsp文件、class類、lib/*.jar包,都是WebClassLoader加載的。Tomcat加載資源的概況圖:
當(dāng)Jsp文件修改的時(shí)候,Tomcat更新步驟:1 但訪問(wèn)1.jsp的時(shí)候,1.jsp的包裝類JspServletWrapper會(huì)去比較1.jsp文件最新修改時(shí)間和上次的修改時(shí)間,以此判斷1.jsp是否修改過(guò)。2 1.jsp修改過(guò)的話,那么jspservletWrapper會(huì)清除相關(guān)引用,包括1.jsp編譯后的servlet實(shí)例和加載這個(gè)servlet的JasperLoader實(shí)例。3 重新創(chuàng)建一個(gè)JasperLoader實(shí)例,重新加載修改過(guò)后的1.jsp,重新生成一個(gè)Servlet實(shí)例。4 返回修改后的1.jsp內(nèi)容給用戶。圖:Jsp清除引用和資源
當(dāng)app下面的class文件修改的時(shí)候,Tomcat更新步驟:1 Context容器會(huì)有專門線程監(jiān)控app下面的類的修改情況。2 如果發(fā)現(xiàn)有類被修改了。那么調(diào)用Context.reload()。清楚一系列相關(guān)的引用和資源。3 然后創(chuàng)新創(chuàng)建一個(gè)WebClassLoader實(shí)例,重新加載app下面需要的class。圖:Context清除引用和資源
在一個(gè)有一定規(guī)模的應(yīng)用中,如果文件修改多次,重啟多次的話,java.lang.OutOfMemoryErrorPermGen space這個(gè)錯(cuò)誤的的出現(xiàn)非常頻繁。主要就是因?yàn)槊看沃貑⒅匦录虞d大量的class,超過(guò)了PermGen space設(shè)置的大小。兩種情況可能導(dǎo)致PermGen space溢出。一、GC(Garbage Collection)在主程序運(yùn)行期對(duì)PermGen space沒(méi)有進(jìn)行清理(GC的不可控行),二、重啟之前WebClassLoader加載的class在別的地方還存在著引用。這里有篇很好的文章介紹了class內(nèi)存泄露-here參考:http://blog.csdn.net/runanli/article/details/2972361(關(guān)于Class類加載器 內(nèi)存泄漏問(wèn)題的探討)http://www.blogjava.net/zhuxing/archive/2008/07/24/217285.html(Java虛擬機(jī)類型卸載和類型更新解析)http://www.ibm.com/developerworks/cn/java/j-lo-hotswapcls/(Java 類的熱替換 —— 概念、設(shè)計(jì)與實(shí)現(xiàn))http://www.iteye.com/topic/136427(classloader體系結(jié)構(gòu))新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注