class卸载、热替换和Tomcat的热部署的分析
这篇文章主要是分析Tomcat中关于热部署和JSP更新替换的原理,在此之前先介绍class的热替换和class的卸载的原理。
一 class的热替换
ClassLoader中重要的方法
http://www.iteye.com/topic/136427(classloader体系结构)
一 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,实现热替换的关键
代码:Hot被用来修改的类
代码:TestHotSwap测试类
在测试类运行的时候,修改Hot.class文件
Hot.class
输出
图: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的时机我们是不可控的,那么同样的我们对于Class的卸载也是不可控的。
例子:
代码:SimpleURLClassLoader,一个简单的自定义classloader
代码:A
运行的时候配置VM参数: -verbose:class;用于查看class的加载与卸载情况。如果用的是Eclipse,在Run Configurations中配置此参数即可。
图:Run Configurations配置

输出结果
图:程序运行中,引用没清楚前,内存中情况

图:垃圾回收后,程序没结束前,内存中情况

三 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类加载器 内存泄漏问题的探讨)
下面看一个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
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(


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); //打印出相关信息
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); //打印出相关信息
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());
改后第五行: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
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(


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,测试类2 // public static final Level CUSTOMLEVEL = new Level("test", 550) {}; // 内部类
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 // 清除相关引用
11 a = null;
12 clazzA = null;
13 loader = null;
14 // 执行一次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 // 清除相关引用
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 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体系结构)
相关推荐
热部署使得开发者能够在运行时动态替换、添加或移除类,而不影响应用程序的正常运行。这种技术对于大型的、复杂的Java应用尤其有用,因为它避免了每次修改代码后都需要重启服务器的时间消耗。 在Java世界中,热部署...
然而,Java虚拟机(JVM)规范并未直接提供对热部署的支持,主要是由于类加载和卸载的机制。 Java的类加载机制基于双亲委派模型,它确保了类加载的安全性和一致性。Bootstrap ClassLoader首先加载JRE的核心库,接着...
Java热加载Class文件技术是一种在不重启应用服务器的情况下更新或替换正在运行的Java类的方法,这对于开发者来说是一项非常实用的功能,因为它极大地提高了开发效率。在传统的开发过程中,修改代码后通常需要停止、...
内容概要:本文详细探讨了在主从博弈框架下,共享储能与综合能源微网的优化运行及其仿真复现。通过MATLAB和CPLEX的联合使用,展示了微网运营商和用户聚合商之间的动态博弈过程。上层模型关注微网运营商的定价策略,旨在最大化利润,考虑售电收益、储能运维成本等因素。下层模型则聚焦于用户聚合商的响应,根据电价调整电热负荷并参与共享储能调度。文中还介绍了电热耦合约束、充放电互斥约束等关键技术细节,并通过迭代博弈实现了策略更新。最终仿真结果显示,在引入电制热设备后,用户侧热负荷弹性提升,博弈收敛速度加快,达到双赢效果。 适合人群:从事能源系统优化、博弈论应用、MATLAB编程的研究人员和技术人员。 使用场景及目标:适用于希望深入了解主从博弈在综合能源系统中应用的学者和工程师。目标是掌握如何通过数学建模和编程实现复杂的能源系统优化,理解电热耦合机制和共享储能的作用。 其他说明:文章提供了详细的代码片段和仿真结果,帮助读者更好地理解和复现实验。此外,还讨论了一些常见的调试问题和解决方案,如约束冲突等。
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
内容概要:深度学习在多个领域有着广泛应用。在计算机视觉方面,涵盖图像分类、目标检测、图像分割等任务,应用于自动驾驶、医疗影像分析等领域;在自然语言处理上,包括机器翻译、文本分类、文本生成等功能,服务于信息检索、内容创作等;语音识别与合成领域,实现了语音到文本的转换以及文本到语音的生成,推动了智能交互的发展;医疗领域,深度学习助力医学影像分析、疾病预测、个性化治疗及健康监测;金融领域,深度学习用于信用风险评估、欺诈检测、高频交易等,保障金融安全并优化投资策略;自动驾驶方面,环境感知与决策控制系统确保车辆安全行驶;娱乐与媒体领域,个性化推荐和内容生成提升了用户体验;工业与制造业中,质量检测和预测性维护提高了生产效率和产品质量。 适合人群:对深度学习及其应用感兴趣的初学者、研究人员以及相关领域的从业者。 使用场景及目标:帮助读者全面了解深度学习在不同行业的具体应用场景,明确各领域中深度学习解决的实际问题,为后续深入研究或项目实施提供方向指引。 其他说明:随着深度学习技术的持续进步,其应用范围也在不断扩大,文中提及的应用实例仅为当前主要成果展示,未来还有更多潜力待挖掘。
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
周梁伟-大模型在融合通信中的应用实践
内容概要:本文详细介绍了利用西门子S7-200 PLC和组态王软件构建的一个花式喷泉控制系统的设计与实现。首先阐述了系统的硬件组成,包括三个环形喷泉组、七彩LED灯带以及功放喇叭等组件,并给出了详细的IO分配表。接着深入解析了关键的梯形图程序逻辑,如自动模式循环、灯光控制、喷泉舞步等部分的具体实现方法。此外,还分享了一些实际调试过程中遇到的问题及其解决方案,例如电源隔离、电磁干扰处理等。最后展示了组态王界面上生动有趣的动画效果设计思路。 适合人群:对PLC编程和工业自动化感兴趣的工程师和技术爱好者。 使用场景及目标:适用于需要设计类似互动娱乐设施的专业人士,旨在帮助他们掌握从硬件选型、程序编写到界面美化的完整流程,从而能够独立完成类似的工程项目。 其他说明:文中不仅提供了理论知识讲解,还包括了许多实践经验教训,对于初学者来说非常有价值。同时,作者还在系统中加入了一些趣味性的元素,如隐藏模式等,增加了项目的吸引力。
内容概要:本文详细介绍了利用COMSOL进行电弧熔池多物理场耦合仿真的方法和技术要点。首先解释了电弧熔池的本质及其复杂性,然后依次讲解了几何建模、材料属性设置、求解器配置以及后处理等方面的具体步骤和注意事项。文中提供了大量实用的MATLAB、Java和Python代码片段,帮助用户更好地理解和应用相关技术。此外,作者分享了许多实践经验,如分阶段激活物理场、使用光滑过渡函数处理相变、优化网格划分等,强调了参数选择和边界条件设定的重要性。 适合人群:从事电弧熔池仿真研究的专业人士,尤其是有一定COMSOL使用经验的研究人员。 使用场景及目标:适用于需要精确模拟电弧熔池行为的研究项目,旨在提高仿真精度并减少计算时间。主要目标是掌握多物理场耦合仿真的关键技术,解决实际工程中遇到的问题。 其他说明:文章不仅提供了详细的理论指导,还包括许多实用的操作技巧和常见错误的解决方案,有助于读者快速上手并深入理解电弧熔池仿真的难点和重点。
9f148310e17f2960fea3ff60af384a37_098bb292f553b9f4ff9c67367379fafd
# 【spring-ai-hanadb-store-1.0.0-M7.jar中文-英文对照文档.zip】 中包含: 中文-英文对照文档:【spring-ai-hanadb-store-1.0.0-M7-javadoc-API文档-中文(简体)-英语-对照版.zip】 jar包下载地址:【spring-ai-hanadb-store-1.0.0-M7.jar下载地址(官方地址+国内镜像地址).txt】 Maven依赖:【spring-ai-hanadb-store-1.0.0-M7.jar Maven依赖信息(可用于项目pom.xml).txt】 Gradle依赖:【spring-ai-hanadb-store-1.0.0-M7.jar Gradle依赖信息(可用于项目build.gradle).txt】 源代码下载地址:【spring-ai-hanadb-store-1.0.0-M7-sources.jar下载地址(官方地址+国内镜像地址).txt】 # 本文件关键字: spring-ai-hanadb-store-1.0.0-M7.jar中文-英文对照文档.zip,java,spring-ai-hanadb-store-1.0.0-M7.jar,org.springframework.ai,spring-ai-hanadb-store,1.0.0-M7,org.springframework.ai.vectorstore.hanadb,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,springframework,spring,ai,hanadb,store,中文-英文对照API文档,手册,开发手册,使用手册,参考手册 # 使用方法: 解压 【spring-ai-hanadb-store-1.0.0-M7.jar中文-英文
# 压缩文件中包含: 中文文档 jar包下载地址 Maven依赖 Gradle依赖 源代码下载地址 # 本文件关键字: jar中文文档.zip,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,开发手册,使用手册,参考手册 # 使用方法: 解压最外层zip,再解压其中的zip包,双击 【index.html】 文件,即可用浏览器打开、进行查看。 # 特殊说明: ·本文档为人性化翻译,精心制作,请放心使用。 ·只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; ·不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 # 温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件;
3dmax插件
内容概要:本文详细介绍了单相全桥PWM整流器采用双闭环控制策略的具体实现方法和技术要点。电压环采用PI控制器来稳定直流侧电压,电流环则使用PR控制器确保交流电流与电压同相位并实现单位功率因数。文中提供了详细的MATLAB、C和Python代码片段,解释了各个控制器的设计思路和参数整定方法。此外,文章还讨论了突加负载测试、电压前馈补偿、PWM生成以及硬件选型等方面的内容,强调了系统的稳定性和快速响应能力。 适合人群:从事电力电子、自动控制领域的工程师和技术人员,尤其是对PWM整流器和双闭环控制感兴趣的读者。 使用场景及目标:适用于需要精确控制直流电压和交流电流的应用场景,如工业电源、新能源发电等领域。目标是提高系统的电能质量和动态响应速度,确保在负载变化时仍能保持稳定的输出。 其他说明:文章不仅提供了理论分析,还包括了大量的实际测试数据和波形图,帮助读者更好地理解和掌握双闭环控制的实际效果。同时,文中提到的一些调试技巧和注意事项对于实际工程应用非常有价值。
easyocr安装包和模型
AC_DIMMER交流调光灯stm32.7z
仲量联行-负责任的房地产:实现社会价值,赋能建筑环境,创造积极的环境和社会影响
C语言全部知识点复习资料.doc