近期一個項目有個定時任務阻塞住了,從日志里看沒有任何異常產生,但就是定時不再執行了,進程還在,jstack看了下線程處於WAIT狀態,但就是不再定時觸發。於是拿代碼分析了一下,代碼原理很簡單,拿ScheduledExecutorService.scheduleWithFixedDelay設定的定時任務,簡化后類似這樣:
public class Application { private static ScheduledExecutorService timer = Executors.newScheduledThreadPool(2); public static void main(String[] args) { timer.scheduleWithFixedDelay(() -> { try { //此處執行任務 } catch(Exception ex) { System.out.println(ex.getMessage()); } }, 0, 1, TimeUnit.SECONDS); } }
一般定時任務掛了,第一考慮的就是任務線程異常了,因為javadoc里關於scheduleWithFixedDelay有這樣的警告:
當有異常產生時,后續定時任務就停掉了。但是代碼里已經考慮了異常情況,做了try/catch,並且沒有輸出錯誤日志,只好修改代碼嘗試跟蹤下線程執行結果:
public class Application { private static ScheduledExecutorService timer = Executors.newScheduledThreadPool(2); public static void main(String[] args) throws ExecutionException, InterruptedException { ScheduledFuture<?> handle = timer.scheduleWithFixedDelay(() -> { try { //此處執行任務 } catch(Exception ex) { System.out.println(ex.getMessage()); } }, 0, 1, TimeUnit.SECONDS); handle.get(); //如果線程執行異常,此處會拋出 } }
用ScheduledFuture跟蹤,上面的測試程序當然不會報錯,但在實際環境里打印了下面的異常:
Exception in thread "pool-1-thread-1" java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError
這下搞清楚了,因為-Xmx參數不合理導致線程在處理某個大數據時拋出OOM,這個錯誤沒有被上面的try/catch捕獲,導致定時任務掛掉。因此使用ScheduledExecutorService時一定要注意考慮各種可能的異常,不過對於OOM,即使捕獲了能做的事也有限,但至少可以保證定時任務能繼續,並且在日志里留下痕跡方便排查。