1. 程式人生 > >"Tomcat+Spring+Quartz"解決方案下,關閉Tomcat出現"執行緒未關閉,出現記憶體洩漏"錯誤

"Tomcat+Spring+Quartz"解決方案下,關閉Tomcat出現"執行緒未關閉,出現記憶體洩漏"錯誤

使用"Tomcat+Spring+Quartz"解決方案,在關閉Tomcat時出現如圖1所示錯誤資訊:

                                                                                                       圖1


這裡使用的Tomcat版本為6.2.32,Spring版本為3.2.3,Quartz版本為1.8.6

一、原因分析

在"Tomcat+Spring+Quartz"的解決方案中,Tomcat在執行的時候,Spring中配置的Quartz SchedulerFactoryBean會建立多個工作執行緒。在Tomcat關閉的時候,檢測到這些執行緒沒有得到及時的銷燬,因而出現如圖1的錯誤。

二、解決方案

Tomcat關閉的時候,會去銷燬Spring例項。根據Spring的生命週期機制,在銷燬Spring例項的過程中,會去處理Spring中配置的Bean例項的銷燬事宜。只要Spring中配置的Bean例項符合以下圖2中的任意一個條件

                                       圖2


Spring例項銷燬過程中就會去呼叫這些Bean例項上的設定的銷燬方法。
在我們的專案中,在Spring中配置Quartz SchedulerFactoryBean的片段如下:
<bean id="startQuartz" lazy-init="false"  autowire="no"  class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref bean="doTime"/>
            </list>
        </property>
</bean>
檢視org.springframework.scheduling.quartz.SchedulerFactoryBean的原始碼,發現它繼承org.springframework.beans.factory.DisposableBean介面,滿足圖2中的條件。因而在Spring例項的銷燬過程中,SchedulerFactoryBean例項的destroy()方法會被自動呼叫。
因而網上很多“使呼叫SchedulerFactoryBean例項的銷燬方法”的解決方案都是錯誤的,這些解決方案包括“在Spring中定義SchedulerFactoryBean例項時增加‘destroy-method’屬性”,“實現一個監聽器,使得在Tomcat銷燬時去呼叫SchedulerFactoryBean例項的銷燬方法”等。
實際上,產生以上錯誤的原因是在銷燬過程中,SchedulerFactoryBean例項的destroy()方法的執行會給它建立的執行緒傳送關閉指令,然後destroy()方法就執行完畢返回。但是其實這些執行緒真正關閉還是需要一定時間的,這產生了時延,因此當destroy()方法執行完畢返回,然後相應的Spring例項的銷燬過程完成之後,Tomcat認為理應沒有執行緒在執行,但是卻發現還有執行緒在執行,因此就會丟擲以上錯誤。
那麼一種解決方案就是讓SchedulerFactoryBean例項的destroy()方法等待一段時間再完成返回,相應的Spring例項的銷燬過程也會等待該段時間,在這段時間內這些Quartz SchedulerFactoryBean的執行緒得以關閉,接著Tomcat檢查的時候就不會丟擲還有執行緒在執行出現記憶體洩漏的錯誤。
我建立瞭如下類:
package com.dslztx.utils;


import org.quartz.SchedulerException;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;


public class SchedulerFactoryBeanWithShutdownDelay extends SchedulerFactoryBean {


    @Override
    public void destroy() throws SchedulerException {
        super.destroy();


        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
相應在Spring中的配置片段如下:
 <bean id="startQuartz" lazy-init="false" autowire="no"  class="com.dslztx.utils.SchedulerFactoryBeanWithShutdownDelay">
        <property name="triggers">
            <list>
                <ref bean="doTime"/>
            </list>
        </property>
</bean>

三、其他

1、由於這些Quartz SchedulerFactoryBean建立的執行緒最後能夠得以關閉,因此Tomcat程序也能被正常關閉,在有些情形中,Tomcat程序內的執行緒不能得以關閉,那麼Tomcat程序也不能被正常關閉,只能通過kill命令,強行殺死程序。

2、這個問題已經在Quartz 2.1上修復

參考文獻:

[1]http://forums.terracotta.org/forums/posts/list/3479.page
[2]http://stackoverflow.com/questions/2730354/spring-scheduler-shutdown-error
[3]https://jira.terracotta.org/jira/browse/QTZ-192
[4]http://forum.spring.io/forum/spring-projects/container/25869-quartz-doesn-t-shutdown