Tomcat reload memoryleak 错误描述 此现象发生在tomcat reload过程中多见。
我们的线上环境是使用jenkins远程部署,在部署重启中出现memory leak错误提示
环境: tomcat7、jdk1.8
问题原因定位: jvm默认定义了三种classloader,分别是bootstrap classloader、extension classloader、system classloader 。采用的是双亲委托机制进行加载。 tomcat7的加载模型如图 注意此处tomcat的类加载方式不完全遵循双亲委派模型,其中对于应用级别的加载各个web应用自己的类加载器(WebAppClassLoader)会优先加载,如果加载不到才走commonClassLoader走双亲方式。 当tomcat发生relaod的时候会执行org.apache.catalina.loader.WebappLoader下的backgroundProcess方法
public void backgroundProcess() { if (reloadable && modified()) { try { Thread.currentThread().setContextClassLoader (WebappLoader.class.getClassLoader()); if (container instanceof StandardContext) { ((StandardContext) container).reload(); } } finally { if (container.getLoader() != null) { Thread.currentThread().setContextClassLoader (container.getLoader().getClassLoader()); } } } else { closeJARs(false); } }此处会判断reloadable 和modified(),即是否允许reload并且产生修改。再看下是如何reload的,此部分在org.apache.catalina.core.StandardContext下的reload方法
public synchronized void reload() { // Validate our current component state if (!getState().isAvailable()) throw new IllegalStateException (sm.getString("standardContext.notStarted", getName())); if(log.isInfoEnabled()) log.info(sm.getString("standardContext.reloadingStarted", getName())); // Stop accepting requests temporarily. setPaused(true); try { stop(); } catch (LifecycleException e) { log.error( sm.getString("standardContext.stoppingContext", getName()), e); } try { start(); } catch (LifecycleException e) { log.error( sm.getString("standardContext.startingContext", getName()), e); } setPaused(false); if(log.isInfoEnabled()) log.info(sm.getString("standardContext.reloadingCompleted", getName())); }那么再回到问题quartz等线程没有被回收,重启前还有在其他地方引用的线程则gc无法回收。就会报上述错误。
解决方法: 1、应用使用的jesery,检测在servlet关闭的时候处理你要关闭的对象和线程
public void contextDestroyed(ServletContextEvent sce) { // TODO Auto-generated method stub System.out.println("reload Tomcat .... waiting >>>> manual close to prevent memory leak !!!!!! --- Today "); //sql this.shutDownSql(); //Jms this.shutDownJms(); //Quertz this.shutDownQuertz(); //Thread this.shutDownThread(); System.out.println("reload Tomcat .... over >>>> Its a happy day !!! --- Today"); }2、Tomcat的实现是通过WeakHashMap来实现弱引用的,将WebappClassLoader对象放置到WeakHashMap中
private Map<ClassLoader, String> childClassLoaders = new WeakHashMap<ClassLoader, String>(); public String[] findReloadedContextMemoryLeaks() { List<String> result = new ArrayList<String>(); for (Map.Entry<ClassLoader, String> entry : childClassLoaders.entrySet()) { ClassLoader cl = entry.getKey(); if (!((WebappClassLoader) cl).isStarted()) { result.add(entry.getValue()); } } return result.toArray(new String[result.size()]); }