這篇文章主要是分析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文件。www.2cto.com
下面看一個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]
......