Made with Remarkable!java
近日,同事反饋說在搭建jenkins 部署,在打包完成,執行自動部署時,執行server tomcat 的shutdown.sh 後,tomcat 進程未關閉,資源未獲得釋放,日誌輸出以下:git
警告: The web application [XXXX] appears to have started a thread named [commons-pool-EvictionTimer] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
java.lang.Object.wait(Native Method)
java.util.TimerThread.mainLoop(Timer.java:552)
java.util.TimerThread.run(Timer.java:505)
十月 13, 2017 10:10:19 上午 org.apache.catalina.loader.WebappClassLoaderBase clearReferencesThreads
警告: The web application [XXXX appears to have started a thread named [Thread-6] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
sun.misc.Unsafe.park(Native Method)
java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
java.util.concurrent.locks.AbstractQueuedSynchronizer $ConditionObject.await(AbstractQueuedSynchronizer.java:2039) java.util.concurrent.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:492) java.util.concurrent.LinkedBlockingDeque.take(LinkedBlockingDeque.java:680) com.erp.cloudfi.report.util.LongTimeWorker.startWork(LongTimeWorker.java:38) com.erp.cloudfi.report.util.LongTimeWorker$ 1.run(LongTimeWorker.java:27)
java.lang.Thread.run(Thread.java:748) github
通過日誌反饋,查詢到 有兩種狀況會形成線程存留.咱們須要在spring 應用 shutdown時,destroy 相關資源並退出相關線程 web
代碼以下: redis
public class LongTimeWorker { /** * 任務隊列 */ BlockingQueue<Worker> works; public LongTimeWorker() { works = new LinkedBlockingDeque<Worker>(); Thread thread = new Thread(new Runnable() { @Override public void run() { startWork(); } }); thread.setDaemon(true); thread.start(); } public void startWork() { try { while(true) { Worker work = works.take(); Worksheet sheet = work.getWorksheet(); work.run(); if(sheet == null){ Thread.sleep(100); }else{ String id = sheet.getId(); existsKey.remove(id); } } } catch (InterruptedException e) { e.printStackTrace(); } } /** * * @param work */ public void setWork(Worker work) { try { works.put(work); } catch (InterruptedException e) { e.printStackTrace(); } } }
能夠看到,在建立對象的時候,以demon 方式啓動了一個 死循環的 程序,不包含退出條件.spring
修改方式以下,添加 退出標誌,放棄建立對象的時候啓動線程,增長啓動線程方法,與退出線程方法.
修改後代碼以下: 數據庫
public class LongTimeWorker { /** * 任務隊列 */ BlockingQueue<Worker> works; private boolean workstatus=false; private Thread workthread = null; public LongTimeWorker() { works = new LinkedBlockingDeque<Worker>(); workthread = new Thread(new Runnable() { @Override public void run() { startWork(); } }); workstatus=false; workthread.setDaemon(true); } public void startWork() { workstatus = true; workthread.start(); } public void stopWork() { workstatus=false; } public void work() { try { while(workstatus) { Worker work = works.take(); Worksheet sheet = work.getWorksheet(); work.run(); if(sheet == null){ Thread.sleep(100); } } } catch (InterruptedException e) { e.printStackTrace(); } } /** * @param work */ public void setWork(Worker work) { try { works.put(work); } catch (InterruptedException e) { e.printStackTrace(); } } }
實現類爲 org.apache.commons.pool.impl.EvictionTimer 所屬於 commons-pool 包下,class 描述以下apache
Provides a shared idle object eviction timer for all pools. This class wraps the standard java.util.Timer and keeps track of how many pools are using it. If no pools are using the timer, it is canceled. This prevents a thread being left running which, in application server environments, can lead to memory leads and/or prevent applications from shutting down or reloading cleanly. This class has package scope to prevent its inclusion in the pool public API. The class declaration below should *not* be changed to public.提供了一個基於timer 機制的共享空閒 對象的通用池.緩存
項目中涉及到 對象共享池的相關有 數據庫鏈接池,JedisPool,由於 未使用spring 提供的 redis 模塊 ,而是基於jedis 進行的封裝.因此針對JedisPool 鏈接池 .(數據庫鏈接池比較經常使用,之前未暴露此問題).通過檢查工程代碼,發現應用中 在應用啓動時,聲明瞭一個JedisPool,來進行業務緩存處理,但在應用退出時並未對jedispool 有任何處理.
參考 https://github.com/xetorthio/jedis/issues/936 tomcat
Pool needs to be closed when it is no longer used - Failed to stop thread named [commons-pool-EvictionTimer]
pool在不被使用的時候須要釋放guys from commons-pool you need to create a ServletContextListener and implement the contextDestroyed method. In thaUImethod you should get the reference to your JedisPool and call the close method.
須要在ServletContextListener 實現 contextDestroyed 方法 , 執行JedisPool 對象的destroy 方法,對資源進行釋放.
a.實現ServletContextListener 接口,分別在contextInitialized 與 contextDestroyed 中實現本身的資源初始化與銷燬邏輯(在 destroy 方法中 處理掉問題中未關閉的線程,與未關閉的redis pool )
public class CloudfiApplicationContextListener implements ServletContextListener { private ServletContext servletContext; private LongTimeWorker longTimeWorker; private MybatisRedisCache redis; public void setRedis(MybatisRedisCache redis) { this.redis = redis; } public void setLongTimeWorker(LongTimeWorker longTimeWorker) { this.longTimeWorker = longTimeWorker; } @Override public void contextInitialized(ServletContextEvent sce) { servletContext = sce.getServletContext(); SpringContextUtil.setApplicationContext(WebApplicationContextUtils.getWebApplicationContext(servletContext)); longTimeWorker = SpringContextUtil.getBean( LongTimeWorker.class); redis = SpringContextUtil.getBean(MybatisRedisCache.class); longTimeWorker.startWork(); } @Override public void contextDestroyed(ServletContextEvent sce) { longTimeWorker.stopWork(); redis.getJedisPool().close(); } }
b.在web.xml 中配置 a 中定義的listener
<servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <listener> <listener-class> javacommon.init.CloudfiApplicationContextListener </listener-class> </listener>
注意,若是須要在實現的listener 中,使用spring 中的bean ,因爲listener 與 servlet 加載順序的限制,此處只能讀取 ContextLoaderListener
掃描到spring 的bean,沒法獲取到 DispatcherServlet 掃描到spring 的bean (若須要獲取 ContextLoaderListener 的bean ,注意自定義listener 的配置須要在 ContextLoaderListener 配置以後)
至此,引發tomcat 執行 shutdown.sh 沒法正常關閉的問題解決.
若是上述中出現錯誤,歡迎指正,如有問題請聯繫smn007@163.com crazy~smn 星期五, 13. 十月 2017 02:55下午