首先纠正一个误区: 热部署不是我们在Eclipse里面修改了代码不用重启就可以持续调试. 详情可以参考上一篇博文: 热部署和"Hot Code Replace"的区别 http://flyfoxs.iteye.com/blog/2078863
热部署有一个缺陷,就是很容易导致内存泄露, 并且不是很容易从代码层次避免. 所以产品环境一般不推荐启用热部署.
JAVA里面经常遇到的内存泄露,是一个无用对象长期被引用导致无法被回收.这种情况只要注意集合对象,基本就能避免大部分问题.这个也是可以测试,或者分析内存对象也能发现.
但是热部署也会导致内存泄露,这个问题却很少被引起注意,因为它并不是很容易重现.并且相比前一种内存泄露,并不是很好理解.下面来对这种内存泄露来做一个简单分析.
- 场景分析:
JVM只提供了加载Class的方法,没有提供卸载的方法.所以针对这种情况就需要使用新的ClassLoader来重新加载新的Class来实现热部署. 大部分情况下旧的ClassLoader及所Loader的对象就会形成一个孤岛,稍后就会被回收.
但是事情不是绝对的,如果恰巧有别的ClassLoader加载了一个对象,并且这个对象引用了孤岛中的某个对象,那么孤岛将不再是孤岛,这个时候内存泄露就会发生了.比如Log4j和热部署加一块,就可能会出现内存泄露
- 场景简化:
使用Tomcat来观察内存泄露比较复杂,既然分析出了是热部署导致的内存泄露.那么我们通过自己实现类加载器的热部署应该同样也可以观察的到内存泄露.
具体的方法就是:
1)通过实现一个Classloader,加载指定的Class并实例化,然后关联到LinkedList上面.
2)监控指定Class的变化,如果有变化就new一个新的ClassLoader对象,然后重新加载Class并实例化对象,然后关联到LinkedList上面.
3)观察新旧ClassLoader生成出来的对象,是否可被垃圾回收.如果不能垃圾回收,在Tomcat进行热部署也会有同样的事情了.
- 代码示例
1)如果MonitorHotSwap.list.size()>1则说明有了内存泄露,详情大家可以看下面的代码, 也可以下载附件自己去运行一下.附件下载地址
2)需要修改Hot类来触发重新加载Class
3)需要根据你自己的项目路径,修改HotSwapURLClassLoader里面的属性值
4)主要代码来自于下面的参考链接,根据需要进行了部分修改.
class MonitorHotSwap implements Runnable { // Hot就是用于修改,用来测试热加载 private String className = "classloader.Hot"; private Class hotClazz = null; private HotSwapURLClassLoader hotSwapCL = null; private List<Object> list = new LinkedList(); @Override public void run() { try { while (true) { initLoad(); Object hot = hotClazz.newInstance(); if(!isExisting(hot)){ list.add(hot); } Method m = hotClazz.getMethod("toString"); m.invoke(hot, null); // 打印出相关信息 System.out.println("========================================"); System.out.println("List size="+list.size()); // 每隔5秒重新加载一次 Thread.sleep(5000); } } catch (Exception e) { e.printStackTrace(); } } /** * 加载class */ void initLoad() throws Exception { hotSwapCL = HotSwapURLClassLoader.getClassLoader(); // 如果Hot类被修改了,那么会重新加载,hotClass也会返回新的 hotClazz = hotSwapCL.loadClass(className); } boolean isExisting(Object obj){ for(Object tempObj : list){ if(tempObj.getClass().getClassLoader().equals(obj.getClass().getClassLoader())){ return true; } } return false; }
- 解决方案:
注册一个Listener, 解除当前WebappClassloader加载的对象和高层ClassLoader加载对象的关联关系. 这样当War报Destory的时候, 可以使当前WebappClassloader加载对象形成一个真正的孤岛,等待回收.
注:
同一个ClassLoader(并不仅仅指的是同一个Class,而是同一个实例),具体来说就是如果一个JVM里面, 如果new了多个ClassLoader,那么他们是不同的ClassLoader.这样当他们Loader相同Class时,这些生成的实例,虽然包名,类名想等,但是却不可能equals.
JSP如何实现热部署
http://www.linuxidc.com/Linux/2013-05/83816.htm
class卸载、热替换和Tomcat的热部署的分析(有示例)
http://www.blogjava.net/heavensay/archive/2012/11/07/389685.html
JVM GC垃圾回收和ClassLoader类加载器之间的微妙关系
http://blog.csdn.net/runanli/article/details/2972361