「Java系列」quartz原理揭秘和源碼解讀

導(dǎo)語:作為java領(lǐng)域最受歡迎的任務(wù)調(diào)度庫之一,quartz為開發(fā)者提供了豐富的任務(wù)調(diào)度功能,比如讓某段程序在每天18:00準時執(zhí)行。本文將通過demo和源碼,講解quartz如何使用、主要功能有哪些、原理是什么,并挑選幾段有用的源碼片段進行解讀。

quartz logo

1、quartz簡介

quartz,即石英的意思,隱喻如石英表般對時間的準確把握。

quartz是一個由java編寫的任務(wù)調(diào)度庫,由OpenSymphony組織開源出來。那么問題來了,任務(wù)調(diào)度是個什么東西?舉個栗子,現(xiàn)在有N個任務(wù)(程序),要求在指定時間執(zhí)行,比如每周二3點執(zhí)行任務(wù)A、每天相隔5s執(zhí)行任務(wù)B等等,這種多任務(wù)擁有多種執(zhí)行策略就是任務(wù)調(diào)度。而quartz的核心作用,是使任務(wù)調(diào)度變得豐富、高效、安全,開發(fā)者只需要調(diào)幾個quartz接口并做簡單配置,即可實現(xiàn)上述需求。

quartz號稱能夠同時對上萬個任務(wù)進行調(diào)度,擁有豐富的功能特性,包括任務(wù)調(diào)度、任務(wù)持久化、可集群化、插件等。目前最新版本是2.2.3,從github[1]上看,2.2.4已在開發(fā)中。

quartz有競品嗎?有,那就是java Timer。quartz相對于java Timer的優(yōu)勢包括任務(wù)持久化、配置更豐富、線程池等等,詳見官網(wǎng)[2]解釋,兩者在Spring上的用法可見這篇文章[^OpenSymphony Quartz和java Timer]。

接下來,筆者將從一個簡單的demo開始,順著demo里使用到的quartz接口,逐個分析quartz主要功能及其原理。限于篇幅,demo中未涉及的功能,本文不涉及,比如集群化等。最后,挑選2段對大家日常開發(fā)有用的源碼進行解讀。

讀這篇文章有什么用

  • 對一個任務(wù)調(diào)度系統(tǒng)產(chǎn)生初步的原理級了解
  • 更正確地使用quartz
  • 學(xué)到如何采用多線程進行任務(wù)調(diào)度的源碼
  • 學(xué)到如何避免GC的源碼
  • 精(xian)力(de)充(dan)沛(teng),隨便找篇技術(shù)文讀讀

[^OpenSymphony Quartz和java Timer]: yaerfengSpring定時器配置的兩種實現(xiàn)方式OpenSymphony Quartz和java Timer詳解

2、使用代碼demo[3]

本文的demo程序由2個java文件和quartz.properties組成,quartz.properties是可選的,因為quartz有默認配置。demo實現(xiàn)從當前時間開始,每隔2s執(zhí)行一次JobImpl類的execute()方法。

  • TestQuartz.java
/**
 * 從當前時間開始,每隔2s執(zhí)行一次JobImpl#execute()
 * @author hlx
 */
public class TestQuartz {
    public static void main(String[] args) throws SchedulerException, InterruptedException {
      // 創(chuàng)建調(diào)度器
      SchedulerFactory schedulerFactory = new StdSchedulerFactory();
      Scheduler scheduler = schedulerFactory.getScheduler();
  
      // 創(chuàng)建任務(wù)
      JobDetail jobDetail = JobBuilder.newJob(JobImpl.class).withIdentity("myJob", "jobGroup").build();
  
      // 創(chuàng)建觸發(fā)器
      // withIntervalInSeconds(2)表示每隔2s執(zhí)行任務(wù)
      Date triggerDate = new Date();
      SimpleScheduleBuilder schedBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever();
      TriggerBuilder<Trigger> triggerBuilder  = TriggerBuilder.newTrigger().withIdentity("myTrigger", "triggerGroup");
      Trigger trigger = triggerBuilder.startAt(triggerDate).withSchedule(schedBuilder).build();

      // 將任務(wù)及其觸發(fā)器放入調(diào)度器
      scheduler.scheduleJob(jobDetail, trigger);
      // 調(diào)度器開始調(diào)度任務(wù)
      scheduler.start();
   }
}
  • JobImpl.java
/**
 * @author hlx
 */
public class JobImpl implements Job {
  public void execute(JobExecutionContext context) {
    System.out.println("job impl running");
  }
}
  • quartz.properties(可選)
#調(diào)度器名,默認名是QuartzScheduler
org.quartz.scheduler.instanceName: TestQuartzScheduler

#============================================================================
# Configure ThreadPool   配置線程池
#============================================================================
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5

#============================================================================
# Configure JobStore  配置任務(wù)存儲方式
#============================================================================
#相當于掃描頻率
org.quartz.jobStore.misfireThreshold: 60000
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore

我們順著注釋看到,TestQuartz.main()依次創(chuàng)建了scheduler(調(diào)度器)、job(任務(wù))、trigger(觸發(fā)器),其中,job指定了JobImpl,trigger保存job的觸發(fā)執(zhí)行策略(每隔2s執(zhí)行一次),scheduler將job和trigger綁定在一起,最后scheduler.start()啟動調(diào)度,每隔2s觸發(fā)執(zhí)行JobImpl.execute(),打印出job impl running。

對于quartz.properties,簡單場景下,開發(fā)者不用自定義配置,使用quartz默認配置即可,但在要求較高的使用場景中還是要自定義配置,比如通過org.quartz.threadPool.threadCount設(shè)置足夠的線程數(shù)可提高多job場景下的運行性能。更詳盡的配置見官網(wǎng)配置說明頁。

如果讓我們設(shè)計一個任務(wù)調(diào)度系統(tǒng),會像quartz那樣將job、trigger、scheduler解藕嗎?quartz這樣設(shè)計的原因,筆者認為有兩點:

  • job與trigger解藕,其實就是將任務(wù)本身和任務(wù)執(zhí)行策略解藕,這樣可以方便實現(xiàn)N個任務(wù)和M個執(zhí)行策略自由組合,比較容易理解;
  • scheduler單獨分離出來,相當于一個指揮官,可以從全局做調(diào)度,比如監(jiān)聽哪些trigger已經(jīng)ready、分配線程等等,如果沒有scheduler,則trigger間會競爭混亂,難以實現(xiàn)諸如trigger優(yōu)先級等功能,也無法合理使用資源。

下面,筆者將分別就job、trigger、scheduler進行原理分析。

3、job(任務(wù))

job由若干個classinterface實現(xiàn)。

Job接口

開發(fā)者想要job完成什么樣的功能,必須且只能由開發(fā)者自己動手來編寫實現(xiàn),比如demo中的JobImpl,這點無容置疑。但要想讓自己的job被quartz識別,就必須按照quartz的規(guī)則來辦事,這個規(guī)則就是job實現(xiàn)類必須實現(xiàn)Job接口,比如JobImpl就實現(xiàn)了Job。

Job只有一個execute(JobExecutionContext),JobExecutionContext保存了job的上下文信息,比如綁定的是哪個trigger。job實現(xiàn)類必須重寫execute(),執(zhí)行job實際上就是運行execute()。

JobDetailImpl類 / JobDetail接口

JobDetailImpl類實現(xiàn)了JobDetail接口,用來描述一個job,定義了job所有屬性及其get/set方法。了解job擁有哪些屬性,就能知道quartz能提供什么樣的能力,下面筆者用表格列出job若干核心屬性。

屬性名 說明
class 必須是job實現(xiàn)類(比如JobImpl),用來綁定一個具體job
name job名稱。如果未指定,會自動分配一個唯一名稱。所有job都必須擁有一個唯一name,如果兩個job的name重復(fù),則只有最前面的job能被調(diào)度
group job所屬的組名
description job描述
durability 是否持久化。如果job設(shè)置為非持久,當沒有活躍的trigger與之關(guān)聯(lián)的時候,job會自動從scheduler中刪除。也就是說,非持久job的生命期是由trigger的存在與否決定的
shouldRecover 是否可恢復(fù)。如果job設(shè)置為可恢復(fù),一旦job執(zhí)行時scheduler發(fā)生hard shutdown(比如進程崩潰或關(guān)機),當scheduler重啟后,該job會被重新執(zhí)行
jobDataMap 除了上面常規(guī)屬性外,用戶可以把任意kv數(shù)據(jù)存入jobDataMap,實現(xiàn)job屬性的無限制擴展,執(zhí)行job時可以使用這些屬性數(shù)據(jù)。此屬性的類型是JobDataMap,實現(xiàn)了Serializable接口,可做跨平臺的序列化傳輸

JobBuilder類

// 創(chuàng)建任務(wù)
JobDetail jobDetail = JobBuilder.newJob(JobImpl.class).withIdentity("myJob", "jobGroup").build();

上面代碼是demo一個片段,可以看出JobBuilder類的作用:接收job實現(xiàn)類JobImpl,生成JobDetail實例,默認生成JobDetailImpl實例。

這里運用了建造者模式:JobImpl相當于Product;JobDetail相當于Builder,擁有job的各種屬性及其get/set方法;JobBuilder相當于Director,可為一個job組裝各種屬性。

4、trigger(觸發(fā)器)

trigger由若干個classinterface實現(xiàn)。

SimpleTriggerImpl類 / SimpleTrigger接口 / Trigger接口

SimpleTriggerImpl類實現(xiàn)了SimpleTrigger接口SimpleTrigger接口繼承了Trigger接口,它們表示觸發(fā)器,用來保存觸發(fā)job的策略,比如每隔幾秒觸發(fā)job。實際上,quartz有兩大觸發(fā)器:SimpleTriggerCronTrigger,限于篇幅,本文僅介紹SimpleTrigger

Trigger諸類保存了trigger所有屬性,同job屬性一樣,了解trigger屬性有助于我們了解quartz能提供什么樣的能力,下面筆者用表格列出trigger若干核心屬性。

在quartz源碼或注釋中,經(jīng)常使用fire(點火)這個動詞來命名屬性名,表示觸發(fā)job。

屬性名 屬性類型 說明
name 所有trigger通用 trigger名稱
group 所有trigger通用 trigger所屬的組名
description 所有trigger通用 trigger描述
calendarName 所有trigger通用 日歷名稱,指定使用哪個Calendar類,經(jīng)常用來從trigger的調(diào)度計劃中排除某些時間段
misfireInstruction 所有trigger通用 錯過job(未在指定時間執(zhí)行的job)的處理策略,默認為MISFIRE_INSTRUCTION_SMART_POLICY。詳見這篇blog^Quartz misfire
priority 所有trigger通用 優(yōu)先級,默認為5。當多個trigger同時觸發(fā)job時,線程池可能不夠用,此時根據(jù)優(yōu)先級來決定誰先觸發(fā)
jobDataMap 所有trigger通用 同job的jobDataMap。假如job和trigger的jobDataMap有同名key,通過getMergedJobDataMap()獲取的jobDataMap,將以trigger的為準
startTime 所有trigger通用 觸發(fā)開始時間,默認為當前時間。決定什么時間開始觸發(fā)job
endTime 所有trigger通用 觸發(fā)結(jié)束時間。決定什么時間停止觸發(fā)job
nextFireTime SimpleTrigger私有 下一次觸發(fā)job的時間
previousFireTime SimpleTrigger私有 上一次觸發(fā)job的時間
repeatCount SimpleTrigger私有 需觸發(fā)的總次數(shù)
timesTriggered SimpleTrigger私有 已經(jīng)觸發(fā)過的次數(shù)
repeatInterval SimpleTrigger私有 觸發(fā)間隔時間

TriggerBuilder類

// 創(chuàng)建觸發(fā)器
// withIntervalInSeconds(2)表示每隔2s執(zhí)行任務(wù)
  Date triggerDate = new Date();
  SimpleScheduleBuilder schedBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever();
  TriggerBuilder<Trigger> triggerBuilder  = TriggerBuilder.newTrigger().withIdentity("myTrigger", "triggerGroup");
  Trigger trigger = triggerBuilder.startAt(triggerDate).withSchedule(schedBuilder).build();

上面代碼是demo一個片段,可以看出TriggerBuilder類的作用:生成Trigger實例,默認生成SimpleTriggerImpl實例。同JobBuilder一樣,這里也運用了建造者模式。

5、scheduler(調(diào)度器)

scheduler主要由StdScheduler類Scheduler接口、StdSchedulerFactory類SchedulerFactory接口、QuartzScheduler類實現(xiàn),它們的關(guān)系見下面UML圖。

scheduler UML圖
// 創(chuàng)建調(diào)度器
  SchedulerFactory schedulerFactory = new StdSchedulerFactory();
  Scheduler scheduler = schedulerFactory.getScheduler();
......
// 將任務(wù)及其觸發(fā)器放入調(diào)度器
  scheduler.scheduleJob(jobDetail, trigger);
// 調(diào)度器開始調(diào)度任務(wù)
  scheduler.start();

上面代碼是demo一個片段,可以看出這里運用了工廠模式,通過factory類(StdSchedulerFactory)生產(chǎn)出scheduler實例(StdScheduler)。scheduler是整個quartz的關(guān)鍵,為此,筆者把demo中用到的scheduler接口的源碼加上中文注釋做個講解。

  • StdSchedulerFactory.getScheduler()源碼
public Scheduler getScheduler() throws SchedulerException {
        // 讀取quartz配置文件,未指定則順序遍歷各個path下的quartz.properties文件
        // 解析出quartz配置內(nèi)容和環(huán)境變量,存入PropertiesParser對象
        // PropertiesParser組合了Properties(繼承Hashtable),定義了一系列對Properties的操作方法,比如getPropertyGroup()批量獲取相同前綴的配置。配置內(nèi)容和環(huán)境變量存放在Properties成員變量中
        if (cfg == null) {
            initialize();
        }
        // 獲取調(diào)度器池,采用了單例模式
        // 其實,調(diào)度器池的核心變量就是一個hashmap,每個元素key是scheduler名,value是scheduler實例
        // getInstance()用synchronized防止并發(fā)創(chuàng)建
        SchedulerRepository schedRep = SchedulerRepository.getInstance();

        // 從調(diào)度器池中取出當前配置所用的調(diào)度器
        Scheduler sched = schedRep.lookup(getSchedulerName());
        
        ......

        // 如果調(diào)度器池中沒有當前配置的調(diào)度器,則實例化一個調(diào)度器,主要動作包括:
        // 1)初始化threadPool(線程池):開發(fā)者可以通過org.quartz.threadPool.class配置指定使用哪個線程池類,比如SimpleThreadPool。先class load線程池類,接著動態(tài)生成線程池實例bean,然后通過反射,使用setXXX()方法將以org.quartz.threadPool開頭的配置內(nèi)容賦值給bean成員變量;
        // 2)初始化jobStore(任務(wù)存儲方式):開發(fā)者可以通過org.quartz.jobStore.class配置指定使用哪個任務(wù)存儲類,比如RAMJobStore。先class load任務(wù)存儲類,接著動態(tài)生成實例bean,然后通過反射,使用setXXX()方法將以org.quartz.jobStore開頭的配置內(nèi)容賦值給bean成員變量;
        // 3)初始化dataSource(數(shù)據(jù)源):開發(fā)者可以通過org.quartz.dataSource配置指定數(shù)據(jù)源詳情,比如哪個數(shù)據(jù)庫、賬號、密碼等。jobStore要指定為JDBCJobStore,dataSource才會有效;
        // 4)初始化其他配置:包括SchedulerPlugins、JobListeners、TriggerListeners等;
        // 5)初始化threadExecutor(線程執(zhí)行器):默認為DefaultThreadExecutor;
        // 6)創(chuàng)建工作線程:根據(jù)配置創(chuàng)建N個工作thread,執(zhí)行start()啟動thread,并將N個thread順序add進threadPool實例的空閑線程列表availWorkers中;
        // 7)創(chuàng)建調(diào)度器線程:創(chuàng)建QuartzSchedulerThread實例,并通過threadExecutor.execute(實例)啟動調(diào)度器線程;
        // 8)創(chuàng)建調(diào)度器:創(chuàng)建StdScheduler實例,將上面所有配置和引用組合進實例中,并將實例存入調(diào)度器池中
        sched = instantiate();

        return sched;
}

上面有個過程是初始化jobStore,表示使用哪種方式存儲scheduler相關(guān)數(shù)據(jù)。quartz有兩大jobStore:RAMJobStoreJDBCJobStore。RAMJobStore把數(shù)據(jù)存入內(nèi)存,性能最高,配置也簡單,但缺點是系統(tǒng)掛了難以恢復(fù)數(shù)據(jù)。JDBCJobStore保存數(shù)據(jù)到數(shù)據(jù)庫,保證數(shù)據(jù)的可恢復(fù)性,但性能較差且配置復(fù)雜。

兩種常見的任務(wù)存儲方式

  • QuartzScheduler.scheduleJob(JobDetail, Trigger)源碼
 public Date scheduleJob(JobDetail jobDetail,
            Trigger trigger) throws SchedulerException {
        // 檢查調(diào)度器是否開啟,如果關(guān)閉則throw異常到上層
        validateState();
        ......
        // 獲取trigger首次觸發(fā)job的時間,以此時間為起點,每隔一段指定的時間觸發(fā)job
        Date ft = trig.computeFirstFireTime(cal);

        if (ft == null) {
            throw new SchedulerException(
                    "Based on configured schedule, the given trigger '" + trigger.getKey() + "' will never fire.");
        }

        // 把job和trigger注冊進調(diào)度器的jobStore
        resources.getJobStore().storeJobAndTrigger(jobDetail, trig);
        // 通知job監(jiān)聽者
        notifySchedulerListenersJobAdded(jobDetail);                
        // 通知調(diào)度器線程
        notifySchedulerThread(trigger.getNextFireTime().getTime());
        // 通知trigger監(jiān)聽者
        notifySchedulerListenersSchduled(trigger);

        return ft;
    }
  • QuartzScheduler.start()源碼
public void start() throws SchedulerException {
        ......
        // 這句最關(guān)鍵,作用是使調(diào)度器線程跳出一個無限循環(huán),開始輪詢所有trigger觸發(fā)job
        // 原理詳見“如何采用多線程進行任務(wù)調(diào)度”
        schedThread.togglePause(false);
        ......
    }

6、quartz線程模型

quartz運行時線程圖

上圖是筆者在eclipse中調(diào)試demo時的線程圖,可以見到,除了第一條主線程外,還有10條工作線程,和1條調(diào)度器線程。

工作線程以{instanceName}_Worker-{[1-10]}命名。線程數(shù)目由quart.properties文件中的org.quartz.threadPool.threadCount配置項指定。所有工作線程都會放在線程池中,即所有工作線程都放在SimpleThreadPool實例的一個LinkedList<WorkerThread>成員變量中。WorkerThreadSimpleThreadPool的內(nèi)部類,這么設(shè)計可能是因為不想繼承SimpleThreadPool但又想調(diào)用其protected方法,或者想隱藏WorkerThread。線程池還擁有兩個LinkedList<WorkerThread>:availWorkersbusyWorkers,分別存放空閑和正在執(zhí)行job的工作線程。

調(diào)度器線程以{instanceName}_QuartzSchedulerThread命名。該線程將根據(jù)trigger找出要待運行job,然后從threadpool中拿出工作線程來執(zhí)行。調(diào)度器線程主體是QuartzSchedulerThread對象。

{instanceName}指的是quart.properties文件中的org.quartz.scheduler.instanceName配置值,這里是TestQuartzScheduler[1-10]表示從1到10的任意數(shù)字。

7、精彩源碼解讀

本節(jié)中,筆者從quartz源碼中挑選了兩段代碼,之所以選擇這兩段代碼,是因為它們實現(xiàn)了線程間通信、加鎖同步、避免GC等功能,對工程師們很有幫助。

如何采用多線程進行任務(wù)調(diào)度

  • QuartzSchedulerThread.java
// 調(diào)度器線程一旦啟動,將一直運行此方法
public void run() {
  ......
  // while()無限循環(huán),每次循環(huán)取出時間將到的trigger,觸發(fā)對應(yīng)的job,直到調(diào)度器線程被關(guān)閉
  // halted是一個AtomicBoolean類變量,有個volatile int變量value,其get()方法僅僅簡單的一句return value != 0,get()返回結(jié)果表示調(diào)度器線程是否開關(guān)
  // volatile修飾的變量,存取必須走內(nèi)存,不能通過cpu緩存,這樣一來get總能獲得set的最新真實值,因此volatile變量適合用來存放簡單的狀態(tài)信息
  // 顧名思義,AtomicBoolean要解決原子性問題,但volatile并不能保證原子性,詳見http://blog.csdn.net/wxwzy738/article/details/43238089
  while (!halted.get()) {
     try {
        // check if we're supposed to pause...
        // sigLock是個Object對象,被用于加鎖同步
        // 需要用到wait(),必須加到synchronized塊內(nèi)
        synchronized (sigLock) {
            while (paused && !halted.get()) {
                try {
                    // wait until togglePause(false) is called...
                    // 這里會不斷循環(huán)等待,直到QuartzScheduler.start()調(diào)用了togglePause(false)
                    // 調(diào)用wait(),調(diào)度器線程進入休眠狀態(tài),同時sigLock鎖被釋放
                    // togglePause(false)獲得sigLock鎖,將paused置為false,使調(diào)度器線程能夠退出此循環(huán),同時執(zhí)行sigLock.notifyAll()喚醒調(diào)度器線程
                    sigLock.wait(1000L);
                } catch (InterruptedException ignore) {}
            }
            ......
        }
        ......
        // 如果線程池中的工作線程個數(shù) > 0
        if(availThreadCount > 0) {
            ......
            // 獲取馬上到時間的trigger
            // 允許取出的trigger個數(shù)不能超過一個閥值,這個閥值是線程池個數(shù)與org.quartz.scheduler.batchTriggerAcquisitionMaxCount配置值間的最小者
            triggers = qsRsrcs.getJobStore().acquireNextTriggers(
                now + idleWaitTime, Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow());
            ......
            // 執(zhí)行與trigger綁定的job
            // shell是JobRunShell對象,實現(xiàn)了Runnable接口
            // SimpleThreadPool.runInThread(Runnable)從線程池空閑列表中取出一個工作線程
            // 工作線程執(zhí)行WorkerThread.run(Runnable),詳見下方WorkerThread的講解
            if (qsRsrcs.getThreadPool().runInThread(shell) == false) { ...... }
        } else {......}
        ......
    } catch(RuntimeException re) {......}
  } // while (!halted)
  ......
}
  • WorkerThread.java
public void run(Runnable newRunnable) {
        synchronized(lock) {
            if(runnable != null) {
                throw new IllegalStateException("Already running a Runnable!");
            }

            runnable = newRunnable;
            lock.notifyAll();
        }
}

// 工作線程一旦啟動,將一直運行此方法
@Override
public void run() {
        boolean ran = false;
        
        // 工作線程一直循環(huán)等待job,直到線程被關(guān)閉,原理同QuartzSchedulerThread.run()中的halted.get()
        while (run.get()) {
            try {
               // 原理同QuartzSchedulerThread.run()中的synchronized (sigLock)
               // 鎖住lock,不斷循環(huán)等待job,當job要被執(zhí)行時,WorkerThread.run(Runnable)被調(diào)用,job運行環(huán)境被賦值給runnable
                synchronized(lock) {
                    while (runnable == null && run.get()) {
                        lock.wait(500);
                    }
                    // 開始執(zhí)行job
                    if (runnable != null) {
                        ran = true;
                        // runnable.run()將觸發(fā)運行job實現(xiàn)類(比如JobImpl.execute())
                        runnable.run();
                    }
                }
            } catch (InterruptedException unblock) {
             ......
            }
        }
        ......
}

總的來說,核心代碼就是在while循環(huán)中調(diào)用Object.wait(),等待可以跳出while循環(huán)的條件成立,當條件成立時,立馬調(diào)度Object.notifyAll()使線程跳出while。通過這樣的代碼,可以實現(xiàn)調(diào)度器線程等待啟動、工作線程等待job等功能。

如何避免GC

Quartz里提供了一種方案,用來避免某些對象被GC。方案其實簡單而實用,就是QuartzScheduler類創(chuàng)建了一個列表ArrayList<Object>(5) holdToPreventGC,如果某對象被add進該列表,則意味著QuartzScheduler實例引用了此對象,那么此對象至少在QuartzScheduler實例存活時不會被GC。

哪些對象要避免GC?通過源碼可看到,調(diào)度器池和db管理器對象被放入了holdToPreventGC,但實際上兩種對象是static的,而static對象屬于GC root,應(yīng)該是不會被GC的,所以即使不放入holdToPreventGC,這兩種對象也不會被GC,除非被class unload或jvm生命結(jié)束。

static變量所指對象在heap中,如果變量不再指向該對象,比如賦值為null,對象會被GC


  1. https://github.com/quartz-scheduler/quartz ?

  2. http://www.quartz-scheduler.org ?

  3. https://github.com/star2478/java-quartz ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容