Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 470|回复: 0

Spring3.2.11与Quartz2.2.1整合时内存泄漏的问题的解决

[复制链接]
  • TA的每日心情
    奋斗
    前天 08:53
  • 签到天数: 773 天

    [LV.10]以坛为家III

    2045

    主题

    2103

    帖子

    71万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    713646
    发表于 2021-8-28 11:24:23 | 显示全部楼层 |阅读模式

      Quartz是一款定时任务调度的开源框架,使用起来比较方便。并且Spring的support包对Quartz有集成。但是笔者在web应用使用的过程中却遇到了内存泄漏的问题。

    问题的产生

     笔者在使用Spring+Quartz的用法如下(熟悉Spring+Quartz的可以跳过直接看问题):

    1.配置Scheduler工厂

    <bean id="schedulerFactory" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    </bean>

    2.编写Job的实现类QuartzJob,用来执行任务

    //这里须继承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表达式,对任务的定时规则进行更改。

     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注入的。

    这种用法在使用过程中没有问题,但在web应用在Tomcat6中热部署时出险了以下严重警告,如果忽略这种警告,在频繁的reload后会造成Tomcat 的 Permgen space OOM。

    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(),执行内容如下:

        /**
         * 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,问题解决。

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-8-11 06:03 , Processed in 0.062070 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

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