class卸載、熱替換和Tomcat的熱部署的分析
這篇文章主要是分析Tomcat中關(guān)于熱部署和JSP更新替換的原理,在此之前先介紹class的熱替換和class的卸載的原理。
一 class的熱替換
ClassLoader中重要的方法
http://www.iteye.com/topic/136427(classloader體系結(jié)構(gòu))
一 class的熱替換
ClassLoader中重要的方法
loadClass
ClassLoader.loadClass(...) 是ClassLoader的入口點。當(dāng)一個類沒有指明用什么加載器加載的時候,JVM默認(rèn)采用AppClassLoader加載器加載沒有加載過的class,調(diào)用的方法的入口就是loadClass(...)。如果一個class被自定義的ClassLoader加載,那么JVM也會調(diào)用這個自定義的ClassLoader.loadClass(...)方法來加載class內(nèi)部引用的一些別的class文件。重載這個方法,能實現(xiàn)自定義加載class的方式,拋棄雙親委托機(jī)制,但是即使不采用雙親委托機(jī)制,比如java.lang包中的相關(guān)類還是不能自定義一個同名的類來代替,主要因為JVM解析、驗證class的時候,會進(jìn)行相關(guān)判斷。
defineClass
系統(tǒng)自帶的ClassLoader,默認(rèn)加載程序的是AppClassLoader,ClassLoader加載一個class,最終調(diào)用的是defineClass(...)方法,這時候就在想是否可以重復(fù)調(diào)用defineClass(...)方法加載同一個類(或者修改過),最后發(fā)現(xiàn)調(diào)用多次的話會有相關(guān)錯誤:
...
java.lang.LinkageError
attempted duplicate class definition
...
...
所以一個class被一個ClassLoader實例加載過的話,就不能再被這個ClassLoader實例再次加載(這里的加載指的是,調(diào)用了defileClass(...)放方法,重新加載字節(jié)碼、解析、驗證。)。而系統(tǒng)默認(rèn)的AppClassLoader加載器,他們內(nèi)部會緩存加載過的class,重新加載的話,就直接取緩存。所與對于熱加載的話,只能重新創(chuàng)建一個ClassLoader,然后再去加載已經(jīng)被加載過的class文件。
下面看一個class熱加載的例子:
代碼:HotSwapURLClassLoader自定義classloader,實現(xiàn)熱替換的關(guān)鍵
代碼:Hot被用來修改的類
代碼:TestHotSwap測試類
在測試類運(yùn)行的時候,修改Hot.class文件
所以HotSwapURLClassLoader是重加載了Hot類 。注意上面,其實當(dāng)加載修改后的Hot時,HotSwapURLClassLoader實例跟加載沒修改Hot的HotSwapURLClassLoader不是同一個。
圖:HotSwapURLClassLoader加載情況

總結(jié):上述類熱加載,需要自定義ClassLoader,并且只能重新實例化ClassLoader實例,利用新的ClassLoader實例才能重新加載之前被加載過的class。并且程序需要模塊化,才能利用這種熱加載方式。
二 class卸載
在Java中class也是可以unload。JVM中class和Meta信息存放在PermGen space區(qū)域。如果加載的class文件很多,那么可能導(dǎo)致PermGen space區(qū)域空間溢出。引起:java.lang.OutOfMemoryErrorPermGen space. 對于有些Class我們可能只需要使用一次,就不再需要了,也可能我們修改了class文件,我們需要重新加載 newclass,那么oldclass就不再需要了。那么JVM怎么樣才能卸載Class呢。
JVM中的Class只有滿足以下三個條件,才能被GC回收,也就是該Class被卸載(unload):
GC的時機(jī)我們是不可控的,那么同樣的我們對于Class的卸載也是不可控的。
例子:
代碼:SimpleURLClassLoader,一個簡單的自定義classloader
代碼:A
運(yùn)行的時候配置VM參數(shù): -verbose:class;用于查看class的加載與卸載情況。如果用的是Eclipse,在Run Configurations中配置此參數(shù)即可。
圖:Run Configurations配置

上面輸出結(jié)果中的確A.class被加載了,然后A.class又被卸載了。這個例子中說明了,即便是class加載進(jìn)了內(nèi)存,也是可以被釋放的。
圖:程序運(yùn)行中,引用沒清楚前,內(nèi)存中情況

圖:垃圾回收后,程序沒結(jié)束前,內(nèi)存中情況

三 Tomcat中關(guān)于類的加載與卸載
Tomcat中與其說有熱加載,還不如說是熱部署來的準(zhǔn)確些。因為對于一個應(yīng)用,其中class文件被修改過,那么Tomcat會先卸載這個應(yīng)用(Context),然后重新加載這個應(yīng)用,其中關(guān)鍵就在于自定義ClassLoader的應(yīng)用。這里有篇文章很好的介紹了tomcat中對于ClassLoader的應(yīng)用,請點擊here。
Tomcat啟動的時候,ClassLoader加載的流程:
1 Tomcat啟動的時候,用system classloader即AppClassLoader加載{catalina.home}/bin里面的jar包,也就是tomcat啟動相關(guān)的jar包。
2 Tomcat啟動類Bootstrap中有3個classloader屬性,catalinaLoader、commonLoader、sharedLoader在Tomcat7中默認(rèn)他們初始化都為同一個StandardClassLoader實例。具體的也可以在{catalina.home}/bin/bootstrap.jar包中的catalina.properites中進(jìn)行配置。
3 StandardClassLoader加載{catalina.home}/lib下面的所有Tomcat用到的jar包。
4 一個Context容器,代表了一個app應(yīng)用。Context-->WebappLoader-->WebClassLoader。并且Thread.contextClassLoader=WebClassLoader。應(yīng)用程序中的jsp文件、class類、lib/*.jar包,都是WebClassLoader加載的。
Tomcat加載資源的概況圖:

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

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

在一個有一定規(guī)模的應(yīng)用中,如果文件修改多次,重啟多次的話,java.lang.OutOfMemoryErrorPermGen space這個錯誤的的出現(xiàn)非常頻繁。主要就是因為每次重啟重新加載大量的class,超過了PermGen space設(shè)置的大小。兩種情況可能導(dǎo)致PermGen space溢出。一、GC(Garbage Collection)在主程序運(yùn)行期對PermGen space沒有進(jìn)行清理(GC的不可控行),二、重啟之前WebClassLoader加載的class在別的地方還存在著引用。這里有篇很好的文章介紹了class內(nèi)存泄露-here
參考:
http://blog.csdn.net/runanli/article/details/2972361(關(guān)于Class類加載器 內(nèi)存泄漏問題的探討)
下面看一個class熱加載的例子:
代碼:HotSwapURLClassLoader自定義classloader,實現(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 * 只要功能是重新加載更改過的.class文件,達(dá)到熱替換的作用
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 //設(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."開頭的類還是會由系統(tǒng)默認(rèn)ClassLoader加載)
46 */
47 @Override
48 public Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException {
49 Class clazz = null;
50 //查看HotSwapURLClassLoader實例緩存下,是否已經(jīng)加載過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."開始,則有系統(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 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(
)調(diào)用的是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
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文件,達(dá)到熱替換的作用
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 //設(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."開頭的類還是會由系統(tǒng)默認(rèn)ClassLoader加載)
46 */
47 @Override
48 public Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException {
49 Class clazz = null;
50 //查看HotSwapURLClassLoader實例緩存下,是否已經(jīng)加載過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."開始,則有系統(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 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(


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
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); //打印出相關(guān)信息
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 }
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); //打印出相關(guān)信息
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 }
在測試類運(yùn)行的時候,修改Hot.class文件
所以HotSwapURLClassLoader是重加載了Hot類 。注意上面,其實當(dāng)加載修改后的Hot時,HotSwapURLClassLoader實例跟加載沒修改Hot的HotSwapURLClassLoader不是同一個。
圖:HotSwapURLClassLoader加載情況

總結(jié):上述類熱加載,需要自定義ClassLoader,并且只能重新實例化ClassLoader實例,利用新的ClassLoader實例才能重新加載之前被加載過的class。并且程序需要模塊化,才能利用這種熱加載方式。
二 class卸載
在Java中class也是可以unload。JVM中class和Meta信息存放在PermGen space區(qū)域。如果加載的class文件很多,那么可能導(dǎo)致PermGen space區(qū)域空間溢出。引起:java.lang.OutOfMemoryErrorPermGen space. 對于有些Class我們可能只需要使用一次,就不再需要了,也可能我們修改了class文件,我們需要重新加載 newclass,那么oldclass就不再需要了。那么JVM怎么樣才能卸載Class呢。
JVM中的Class只有滿足以下三個條件,才能被GC回收,也就是該Class被卸載(unload):
- 該類所有的實例都已經(jīng)被GC。
- 加載該類的ClassLoader實例已經(jīng)被GC。
- 該類的java.lang.Class對象沒有在任何地方被引用。
GC的時機(jī)我們是不可控的,那么同樣的我們對于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 //設(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."開頭的類還是會由系統(tǒng)默認(rèn)ClassLoader加載)
39 */
40 @Override
41 public Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException {
42 Class clazz = null;
43 //查看HotSwapURLClassLoader實例緩存下,是否已經(jīng)加載過class
44 clazz = findLoadedClass(name);
45 if (clazz != null ) {
46 if (resolve){
47 resolveClass(clazz);
48 }
49 return (clazz);
50 }
51
52 //如果類的包名為"java."開始,則有系統(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
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 //設(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."開頭的類還是會由系統(tǒng)默認(rèn)ClassLoader加載)
39 */
40 @Override
41 public Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException {
42 Class clazz = null;
43 //查看HotSwapURLClassLoader實例緩存下,是否已經(jīng)加載過class
44 clazz = findLoadedClass(name);
45 if (clazz != null ) {
46 if (resolve){
47 resolveClass(clazz);
48 }
49 return (clazz);
50 }
51
52 //如果類的包名為"java."開始,則有系統(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(


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,測試類2 // public static final Level CUSTOMLEVEL = new Level("test", 550) {}; // 內(nèi)部類
3 }
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
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)行的時候配置VM參數(shù): -verbose:class;用于查看class的加載與卸載情況。如果用的是Eclipse,在Run Configurations中配置此參數(shù)即可。
圖:Run Configurations配置

上面輸出結(jié)果中的確A.class被加載了,然后A.class又被卸載了。這個例子中說明了,即便是class加載進(jìn)了內(nèi)存,也是可以被釋放的。
圖:程序運(yùn)行中,引用沒清楚前,內(nèi)存中情況

圖:垃圾回收后,程序沒結(jié)束前,內(nèi)存中情況

1、有啟動類加載器加載的類型在整個運(yùn)行期間是不可能被卸載的(jvm和jls規(guī)范).
2、被系統(tǒng)類加載器和標(biāo)準(zhǔn)擴(kuò)展類加載器加載的類型在運(yùn)行期間不太可能被卸載,因為系統(tǒng)類加載器實例或者標(biāo)準(zhǔn)擴(kuò)展類的實例基本上在整個運(yùn)行期間總能直接或者間接的訪問的到,其達(dá)到unreachable的可能性極小.(當(dāng)然,在虛擬機(jī)快退出的時候可以,因為不管ClassLoader實例或者Class(java.lang.Class)實例也都是在堆中存在,同樣遵循垃圾收集的規(guī)則).
3、被開發(fā)者自定義的類加載器實例加載的類型只有在很簡單的上下文環(huán)境中才能被卸載,而且一般還要借助于強(qiáng)制調(diào)用虛擬機(jī)的垃圾收集功能才可以做到.可以預(yù)想,稍微復(fù)雜點的應(yīng)用場景中(尤其很多時候,用戶在開發(fā)自定義類加載器實例的時候采用緩存的策略以提高系統(tǒng)性能),被加載的類型在運(yùn)行期間也是幾乎不太可能被卸載的(至少卸載的時間是不確定的).
綜合以上三點, 一個已經(jīng)加載的類型被卸載的幾率很小至少被卸載的時間是不確定的.同時,我們可以看的出來,開發(fā)者在開發(fā)代碼時候,不應(yīng)該對虛擬機(jī)的類型卸載做任何假設(shè)的前提下來實現(xiàn)系統(tǒng)中的特定功能.
三 Tomcat中關(guān)于類的加載與卸載
Tomcat中與其說有熱加載,還不如說是熱部署來的準(zhǔn)確些。因為對于一個應(yīng)用,其中class文件被修改過,那么Tomcat會先卸載這個應(yīng)用(Context),然后重新加載這個應(yīng)用,其中關(guān)鍵就在于自定義ClassLoader的應(yīng)用。這里有篇文章很好的介紹了tomcat中對于ClassLoader的應(yīng)用,請點擊here。
Tomcat啟動的時候,ClassLoader加載的流程:
1 Tomcat啟動的時候,用system classloader即AppClassLoader加載{catalina.home}/bin里面的jar包,也就是tomcat啟動相關(guān)的jar包。
2 Tomcat啟動類Bootstrap中有3個classloader屬性,catalinaLoader、commonLoader、sharedLoader在Tomcat7中默認(rèn)他們初始化都為同一個StandardClassLoader實例。具體的也可以在{catalina.home}/bin/bootstrap.jar包中的catalina.properites中進(jìn)行配置。
3 StandardClassLoader加載{catalina.home}/lib下面的所有Tomcat用到的jar包。
4 一個Context容器,代表了一個app應(yīng)用。Context-->WebappLoader-->WebClassLoader。并且Thread.contextClassLoader=WebClassLoader。應(yīng)用程序中的jsp文件、class類、lib/*.jar包,都是WebClassLoader加載的。
Tomcat加載資源的概況圖:

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

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

在一個有一定規(guī)模的應(yīng)用中,如果文件修改多次,重啟多次的話,java.lang.OutOfMemoryErrorPermGen space這個錯誤的的出現(xiàn)非常頻繁。主要就是因為每次重啟重新加載大量的class,超過了PermGen space設(shè)置的大小。兩種情況可能導(dǎo)致PermGen space溢出。一、GC(Garbage Collection)在主程序運(yùn)行期對PermGen space沒有進(jìn)行清理(GC的不可控行),二、重啟之前WebClassLoader加載的class在別的地方還存在著引用。這里有篇很好的文章介紹了class內(nèi)存泄露-here
參考:
http://blog.csdn.net/runanli/article/details/2972361(關(guān)于Class類加載器 內(nèi)存泄漏問題的探討)
http://www.aygfsteel.com/zhuxing/archive/2008/07/24/217285.html(Java虛擬機(jī)類型卸載和類型更新解析)
http://www.ibm.com/developerworks/cn/java/j-lo-hotswapcls/(Java 類的熱替換 —— 概念、設(shè)計與實現(xiàn))http://www.iteye.com/topic/136427(classloader體系結(jié)構(gòu))
posted on 2012-11-07 22:29 heavensay 閱讀(22699) 評論(7) 編輯 收藏 所屬分類: Java