Quartz是一款定時任務調度的開源框架,使用起來比較方便。而且Spring的support包對Quartz有集成。可是筆者在web應用使用的過程當中卻遇到了內存泄漏的問題。web
筆者在使用Spring+Quartz的用法以下(熟悉Spring+Quartz的能夠跳過直接看問題):spring
1.配置Scheduler工廠apache
<bean id="schedulerFactory" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> </bean>
2.編寫Job的實現類QuartzJob,用來執行任務app
//這裏須繼承spring的QuartzJobBean public class QuartzJob extends QuartzJobBean {/** * 任務執行內容。 */ @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { // 執行任務的代碼 } }
3.將Scheduler工廠生產的scheduler注入到業務邏輯類中。這裏注意,注入的實際上是由schedulerFactory生產出來的scheduler實例(對Spring不熟悉的人會天然而然的認爲注入的是schedulerFactory的實例,這是由於Spring的SchedulerFactoryBean實現了FactoryBean接口,因此注入的結果會是FactoryBean.getObject()得到的實例,題外話)。框架
<bean id="taskService" class="com.xxxx.xx.service.impl.TaskServiceImpl" init-method="init" scope="singleton"> <property name="scheduler"> <ref bean="schedulerFactory"/> </property> </bean>
public class TaskServiceImpl { private Scheduler scheduler;/** * 由spring經過工廠注入,此實例爲單例。 * @param scheduler */ public void setScheduler(Scheduler scheduler) { this.scheduler = scheduler; }
4.在業務邏輯類TaskServiceImpl中,使用scheduler執行任務。這裏能夠動態對任務執行增長、刪除、重啓等等操做。還有一個重要的特性是能夠動態的改變Cron表達式,對任務的定時規則進行更改。ide
1 /** 2 * 新增任務。 3 * @param jobName 4 * @param jobGroup 5 * @param cron 6 */ 7 public void addJob(String jobName, String jobGroup, String cron, String kindId) { 8 try { 9 if (StringUtil.isEmpty(jobName) || StringUtil.isEmpty(jobGroup) || StringUtil.isEmpty(cron)) { 10 throw new BusinessException("定時任務建立失敗,參數爲空"); 11 } 12 JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class).withIdentity(jobName, jobGroup).build(); 13 jobDetail.getJobDataMap().put("JobKind", kindId); 14 //表達式調度構建器 15 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron); 16 //按新的cronExpression表達式構建一個新的trigger 17 CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).withSchedule(scheduleBuilder).build(); 18 scheduler.scheduleJob(jobDetail, trigger); 19 log.info("新增定時任務,name:" + jobName + ",group:" + jobGroup + ",cron:" + cron); 20 } catch (SchedulerException e) { 21 // 對異常的處理 22 } 23 }
注意18行的scheduler是由Spring注入的。ui
這種用法在使用過程當中沒有問題,但在web應用在Tomcat6中熱部署時出險瞭如下嚴重警告,若是忽略這種警告,在頻繁的reload後會形成Tomcat 的 Permgen space OOM。this
Jun 24, 2014 5:14:38 AM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [/feeder##1.5.0] appears to have started a thread named [scheduler_Worker-1] but has failed to stop it. This is very likely to create a memory leak.
Jun 24, 2014 5:14:38 AM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [/feeder##1.5.0] appears to have started a thread named [scheduler_Worker-2] but has failed to stop it. This is very likely to create a memory leak.
Jun 24, 2014 5:14:38 AM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [/feeder##1.5.0] appears to have started a thread named [scheduler_Worker-3] but has failed to stop it. This is very likely to create a memory leak.
Jun 24, 2014 5:14:38 AM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [/feeder##1.5.0] appears to have started a thread named [scheduler_Worker-4] but has failed to stop it. This is very likely to create a memory leak.
Jun 24, 2014 5:14:38 AM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [/feeder##1.5.0] appears to have started a thread named [scheduler_Worker-5] but has failed to stop it. This is very likely to create a memory leak.
Jun 24, 2014 5:14:38 AM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [/feeder##1.5.0] appears to have started a thread named [scheduler_Worker-6] but has failed to stop it. This is very likely to create a memory leak.
Jun 24, 2014 5:14:38 AM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
究其緣由是開啓的scheduler_Worker線程沒有關閉。但Spring的SchedulerFactoryBean實現了DisposableBean接口,表示web容器關閉時會執行destroy(),執行內容以下:spa
/** * Shut down the Quartz scheduler on bean factory shutdown, * stopping all scheduled jobs. */ public void destroy() throws SchedulerException { logger.info("Shutting down Quartz Scheduler"); this.scheduler.shutdown(this.waitForJobsToCompleteOnShutdown); }
表示工廠已經作了shutdown。因此,問題出如今真正執行的scheduler.shutdown(true)。線程
原來這是Quartz的bug(見https://jira.terracotta.org/jira/browse/QTZ-192),在調用scheduler.shutdown(true)後,Quartz檢查線程並無等待那些worker線程被中止就結束了。
網上說在Quartz2.1版本已經修補了這個bug,但筆者使用的2.2.1版本仍舊有問題。
那麼問題如何解決,這裏有一個不太美觀的方案:在調用scheduler.shutdown(true)後,增長一點睡眠時間,等待worker線程所有中止。以下:
1。自定義schedulerFactory繼承Spring的SchedulerFactoryBean,覆蓋destroy()增長延時。
public class SchedulerFactoryBeanWithShutdownDelay extends SchedulerFactoryBean { /** * 關於Quartz內存泄漏的不太美觀的解決方案: * 在調用scheduler.shutdown(true)後增長延時,等待worker線程結束。 */ @Override public void destroy() throws SchedulerException { super.destroy(); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }
2。把spring配置文件中的SchedulerFactoryBean替換爲自定義的工廠便可。
<bean id="schedulerFactory" class="com.sinosoft.ms.task.SchedulerFactoryBeanWithShutdownDelay"> </bean>
OK,問題解決。