设为首页 收藏本站
查看: 1414|回复: 0

[经验分享] memory leak-----tomcat日志warn

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2015-8-8 09:58:31 | 显示全部楼层 |阅读模式
  web应用借助于结构:spring mvc + quartz结构,部署到tomcat容器时,shutdown时的error信息:



appears to have started a thread named [schedulerFactoryBean_Worker-1] but has failed to stop it. This is very likely to create a memory leak

  quartz的thread终止的确保,加了个servletListener,工作就是显示的使用shutdown方法:



@Override
public void contextDestroyed(ServletContextEvent sce) {
LogAgent.debug(LoggerEnum.SYS_INFO, "QUARTZ.context destroy start.");
WebApplicationContext webApplicationContext = (WebApplicationContext) sce.getServletContext()
.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
org.springframework.scheduling.quartz.SchedulerFactoryBean fact = (org.springframework.scheduling.quartz.SchedulerFactoryBean) webApplicationContext
.getBean("schedulerFactoryBean");
if (fact != null) {
try {
LogAgent.debug(LoggerEnum.SYS_INFO, "shedule shutdown start.");
fact.getScheduler().shutdown();
LogAgent.debug(LoggerEnum.SYS_INFO, "shedule shutdown end.");
} catch (SchedulerException e) {
LogAgent.error(LoggerEnum.ERROR_INFO, e);
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
LogAgent.error(LoggerEnum.ERROR_INFO, e);
}
LogAgent.debug(LoggerEnum.SYS_INFO, "QUARTZ.context destroy end.");
}
  接着的提示信息:



created a ThreadLocal with key of type [org.apache.commons.lang.builder.ReflectionToStringBuilder$1] (value [org.apache.commons.lang.builder.ReflectionToStringBuilder$1@1a08777c]) and a value of type [java.util.HashSet] (value [[]]) but failed to remove it when the web application was stopped. This is very likely to create a memory leak.
  检查了代码,发现日志输出的部分,有个类使用了类:org.apache.commons.lang.builder.ToStringBuilder,其内部实现使用了org.apache.commons.lang.builder.ReflectionToStringBuilder,再跟进的时候发现,ToStringBuilder的append方法,在append和toString的时候,向ToStringStyle register或unregister(就是threadlocal的set obj和 remove);跟踪代码时发现明显该threadlocal在每次toString时都有remove,不应该会出现leak memory的倾向才对(若运行一半异常还是有可能,但该方法就是一个日志记录,所以无这方面的担心),最后检查pom时发现,commons-lang有两个包,分别是appache-lang和commons-lang:jar名都是commons-lang,删掉appache-lang的这个warn警告就没出现了(我也不记得了,为什么会引入两个commons-lang)。
  
结果再运行时,发现另一个错误了:



信息: Illegal access: this web application instance has been stopped already.  Could not load ch.qos.logback.core.status.WarnStatus.  The eventual following stack trace is caused by an error thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access, and has no functional impact.
java.lang.IllegalStateException
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1588)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1547)
at ch.qos.logback.classic.LoggerContext.noAppenderDefinedWarning(LoggerContext.java:175)
at ch.qos.logback.classic.Logger.callAppenders(Logger.java:267)
at ch.qos.logback.classic.Logger.buildLoggingEventAndAppend(Logger.java:442)
at ch.qos.logback.classic.Logger.filterAndLog_0_Or3Plus(Logger.java:396)
at ch.qos.logback.classic.Logger.debug(Logger.java:503)
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:612)
  首先级别上来看,是信息,不是错误;
  但是本着看到error stack就不爽的本质,了解了一把,原因大致如此:


  • quartz的SimpleThreadPool在初始化的时候,启动了一定数量的线程:WorkerThread,然后它就让WorkerThread自己滴去干活了。
  • 然后当容器发出shutdown的信号时,pool就告诉workerthread们,要shutdown了,现在问题来了,看代码:



/**
*
* Signal the thread that it should terminate.
*
*/
void shutdown() {
run.set(false);
}
  很明显,这就是它的shutdown方法,它并不是要自己杀掉自己,而是告知自己的当前状态是false了,再来看看该状态作用的地方:



1 @Override
2         public void run() {
3             boolean ran = false;
4            
5             while (run.get()) {
6                 try {
7                     synchronized(lock) {
8                         while (runnable == null && run.get()) {
9                             lock.wait(500);
10                         }
11
12                         if (runnable != null) {
13                             ran = true;
14                             runnable.run();
15                         }
16                     }
17                 } catch (InterruptedException unblock) {
18                     // do nothing (loop will terminate if shutdown() was called
19                     try {
20                         getLog().error("Worker thread was interrupt()'ed.", unblock);
21                     } catch(Exception e) {
22                         // ignore to help with a tomcat glitch
23                     }
24                 } catch (Throwable exceptionInRunnable) {
25                     try {
26                         getLog().error("Error while executing the Runnable: ",
27                             exceptionInRunnable);
28                     } catch(Exception e) {
29                         // ignore to help with a tomcat glitch
30                     }
31                 } finally {
32                     synchronized(lock) {
33                         runnable = null;
34                     }
35                     // repair the thread in case the runnable mucked it up...
36                     if(getPriority() != tp.getThreadPriority()) {
37                         setPriority(tp.getThreadPriority());
38                     }
39
40                     if (runOnce) {
41                            run.set(false);
42                         clearFromBusyWorkersList(this);
43                     } else if(ran) {
44                         ran = false;
45                         makeAvailable(this);
46                     }
47
48                 }
49             }
50
51             //if (log.isDebugEnabled())
52             try {
53                 getLog().debug("WorkerThread is shut down.");
54             } catch(Exception e) {
55                 // ignore to help with a tomcat glitch
56             }
57         }
  其实就是第5行,对run进行判断,如果是false,就直接退出了。
  所以pool的shut通知并不是马上就能被执行,如果线程正好走while判断条件,马上就会被退出;
  如果该thread无任务可做时,见第8行:lock.wait(500),所以会wait,这个时候再退出,结果去getLog的时候,loggerContextListener.destroy先执行,无logger了,所以会有该信息。
  



  • 解决办法就是:
  将log的listener放到最后(如果有多个listener的话):




com.xxx.listener.QuartzLitener


com.xxx.web.listener.LogbackListener

  
  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-95359-1-1.html 上篇帖子: Tomcat+Nginx+Lvs部署方案与性能调优 下篇帖子: tomcat 7.0/jdk1.7 配置
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表