之前寫的一個Sql轉發應用出現了內存溢出問題,經過排查發現是ExecutorService沒有正確的進行關閉。
正常來說如果我們將ExecutorService設計成一個靜態變量,那么通常我們是不用去管理其是否關閉的,我們只需要對其本身的線程進行維護操作,ExecutorService對象不用我們顯示的進行維護操作。但是維護靜態線程池對象的不足之處在於,不好去界定池量級的大小,如果太小會導致線程過多的時候線程運行得不到保證,如果過大則通常線程執行時是對內存的一種浪費。所以在調用頻率不高的時候,我們往往會在一次調用的時候創建一個ExecutorService,用這個來進行線程池的管理,那么這里就出現了我們遇到的問題,那就是這個ExecutorService其實是需要手動進行關閉的。
上圖中ExecutorService就沒有正確的關閉。
為什么ExecutorService不會自己進行回收呢,我們通過對其API的閱讀可以得到答案,若是想要ExecutorService完全關閉需要同時滿足三個條件:
1.線程池中沒有正在執行的task任務
2.線程池中沒有等待執行的task任務
3.無法再提交任務到ExecutorService
顯然,如果我們不進行手動關閉的話,第三個條件將無法滿足,導致無法其內存無法被回收,而下一次的調用又繼續進行了線程池的創建,這樣內存迅速的上升,最后導致了OOM。
那么知道了問題的原因,解決方法其實就比較簡單了,我們在使用完之后再調用一個shutdown或者shutdownNow,讓線程池狀態變成不再能接受線程任務就行了。
但是這個時候就有了新的問題,如果某個線程阻塞,一直無法關閉,shutdown方法也不能完成自己的使命,但是如果使用shutdownNow方法的話,就會出現關閉的時候還有線程任務沒有完成。這個時候要怎么合理的進行線程任務的關閉呢。
這個時候我們可以設定一個最大超時時間,如果執行shutdown之后經過超時時間還有任務沒有結束,那我們就用shutdownNow方法來幫他們上路。