12

I'm using quartz in my project. My web application has apparently caused a memory leak when it stops, the error is :

SEVERE: A web application appears to have started a TimerThread named [Timer-12] via the java.util.Timer API but has failed to stop it. To prevent a memory leak, the timer (and hence the associated thread) has been forcibly cancelled. 
Jan 2, 2013 6:55:35 AM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: A web application appears to have started a thread named [DefaultQuartzScheduler_Worker-1] but has failed to stop it. This is very likely to create a memory leak.

I used org.quartz.ee.servlet.QuartzInitializerServlet and org.quartz.ee.servlet.QuartzInitializerListener. The code for my factory is:

StdSchedulerFactory factory = (StdSchedulerFactory) context.getAttribute(QuartzInitializerListener.QUARTZ_FACTORY_KEY );

and settings for quartz in web.xml is :

<servlet>
         <servlet-name>
             QuartzInitializer
         </servlet-name>
         <display-name>
             Quartz Initializer Servlet
         </display-name>
         <servlet-class>
             org.quartz.ee.servlet.QuartzInitializerServlet
         </servlet-class>
         <load-on-startup>
             1
         </load-on-startup>
         <init-param>
             <param-name>shutdown-on-unload</param-name>
             <param-value>true</param-value>
         </init-param>
         <init-param>
             <param-name>wait-on-shutdown</param-name>
             <param-value>true</param-value>
         </init-param>
         <init-param>
             <param-name>start-scheduler-on-load</param-name>
             <param-value>true</param-value>
         </init-param>
     </servlet>
     <context-param>
         <param-name>quartz:shutdown-on-unload</param-name>
         <param-value>true</param-value>
     </context-param>
     <context-param>
         <param-name>quartz:wait-on-shutdown</param-name>
         <param-value>true</param-value>
     </context-param>
     <context-param>
         <param-name>quartz:start-on-load</param-name>
         <param-value>true</param-value>
     </context-param>
     <listener>
         <listener-class>
             org.quartz.ee.servlet.QuartzInitializerListener
         </listener-class>
     </listener>

please help me to solve this memory leak !!

STW
  • 44,917
  • 17
  • 105
  • 161
Ali Farozi
  • 193
  • 2
  • 2
  • 6

4 Answers4

4

By implementing org.quartz.InterruptableJob you can properly interrupt threads triggered by servlet unloading.

@DisallowConcurrentExecution
public class Job implements InterruptableJob {

    private Thread thread;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        thread = Thread.currentThread();
        // ... do work
    }

    @Override
    public void interrupt() throws UnableToInterruptJobException {
        thread.interrupt();
        try {
            thread.join();
        } catch (InterruptedException e) {
            throw new UnableToInterruptJobException(e);
        } finally {
            // ... do cleanup
        }
    }
}

This example may cause a race condition bug on the thread variable, if the job has not been executed before it is interrupted. I leave the final solution open for suggestions, depending on the life cycle of the target application. If you need concurrent execution through the same job instance, augment the solution to handle multiple threads and remove the @DisallowConcurrentExecution annotation.

In order for this to work the quartz property org.quartz.scheduler.interruptJobsOnShutdownWithWait must be set to true. This can be done by defining a property file for the scheduler, or by a bean references if using spring framework.

Example quartz.properties file:

org.quartz.scheduler.interruptJobsOnShutdownWithWait=true

Note that the interruption only is dispatched if the scheduler is configured to wait on shutdown, resulting in a call to scheduler.shutdown(true).

Martin
  • 2,347
  • 1
  • 21
  • 21
2

I see you initialize two instances... - first through org.quartz.ee.servlet.QuartzInitializerServlet - second through org.quartz.ee.servlet.QuartzInitializerListener

Either remove QuartzInitializerServlet or QuartzInitializerListener (and also corresponding parameters)... If you want to have multiple instances (for specific reasons), go with the QuartzInitializerServlet (and do not forget to use different per instance)

MarMax
  • 21
  • 1
  • 3
1

If you are using your own implementation of the ServletContextListener interface for your web application, you can shutdown Quartz gracefully in the contextDestroyed method. Please find below the sample code for Quartz version 2.1.7.

Your job:

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class CronJob implements Job {
    public void execute(JobExecutionContext context)
            throws JobExecutionException {
        // TODO: do you job
    }
}

Your job scheduler:

import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

public class CronJobScheduler {

    private static CronJobScheduler instance = new CronJobScheduler();  
    private Scheduler scheduler;

    private CronJobScheduler() {    
        try {
            scheduler = new StdSchedulerFactory().getScheduler();
        } catch (SchedulerException e) {
            // TODO
        }
    }

    public static CronJobScheduler getInstance() {
        return instance;
    }

    public void trigger() {
        JobKey jobKey = JobKey.jobKey("myJobName", "myJobGroup");       
        JobDetail job = JobBuilder.newJob(CronJob.class).withIdentity(jobKey).build();

        Trigger trigger = TriggerBuilder
                .newTrigger()
                .withIdentity("myTriggerName", "myJobGroup")
                .withSchedule(CronScheduleBuilder.cronSchedule("0 0 1,13 * * ?"))
                .build();

        try {
            scheduler.start();
            scheduler.scheduleJob(job, trigger);
        } catch (SchedulerException e) {    
            // TODO
        }
    }

    public void shutdown(boolean waitForJobsToComplete) {
        try {
            scheduler.shutdown(waitForJobsToComplete);
        } catch (SchedulerException e) {
            // TODO
        }
    }

}

Your implementation of the ServletContextListener interface:

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class MyServletContextListener implements ServletContextListener {

    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
        CronJobScheduler.getInstance().shutdown(true);
    }

    @Override
    public void contextInitialized(ServletContextEvent arg0) {
        CronJobScheduler.getInstance().trigger();
    }

}

Your web.xml

<listener>
    <listener-class>my.package.name.MyServletContextListener</listener-class>
</listener>
justcode
  • 1,562
  • 3
  • 14
  • 25
Yuci
  • 27,235
  • 10
  • 114
  • 113
0

I think you want:

     <init-param>
         <param-name>wait-on-shutdown</param-name>
         <param-value>true</param-value>
     </init-param>

You have a "quartz:" prefix which may be causing Quartz to revert to the default value of "false" for that configuration setting.

Christopher Schultz
  • 20,221
  • 9
  • 60
  • 77
  • no ، "quartz:" prefix is correct. please visit:http://quartz-scheduler.org/api/2.0.0/org/quartz/ee/servlet/QuartzInitializerListener.html . – Ali Farozi May 22 '12 at 18:14
  • You are reading the wrong documentation: your link is to the "Listener" and you are configuring the "Servlet". For some reason, they don't behave in the same way. See: http://quartz-scheduler.org/api/2.0.0/org/quartz/ee/servlet/QuartzInitializerServlet.html – Christopher Schultz May 22 '12 at 18:52
  • this setting is for org.quartz.ee.servlet.QuartzInitializerServlet and "quartz:" prefix is for org.quartz.ee.servlet.QuartzInitializerListener . However, i delete "quartz:" prefix but not correct memory leak. – Ali Farozi May 23 '12 at 02:51
  • I would go and ask the Quartz folks, then, if you don't hear anything on this topic in SE for a while. – Christopher Schultz May 23 '12 at 19:28