日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

一个线程池 bug 引发的 GC 思考!

發布時間:2025/3/21 编程问答 15 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一个线程池 bug 引发的 GC 思考! 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

問題描述

前幾天在幫同事排查生產一個線上偶發的線程池錯誤,邏輯很簡單,線程池執行了一個帶結果的異步任務。

但是最近有偶發的報錯:

java.util.concurrent.RejectedExecutionException:?Task?java.util.concurrent.FutureTask@a5acd19?rejected?from?java.util.concurrent.ThreadPoolExecutor@30890a38\[Terminated,?pool?size?=?0,?active?threads?=?0,?queued?tasks?=?0,?completed?tasks?=?0\]

本文中的模擬代碼已經問題都是在HotSpot java8 (1.8.0_221)版本下模擬&出現的

下面是模擬代碼,通過Executors.newSingleThreadExecutor創建一個單線程的線程池,然后在調用方獲取Future的結果

public?class?ThreadPoolTest?{????public?static?void?main(String[]?args)?{final?ThreadPoolTest?threadPoolTest?=?new?ThreadPoolTest();for?(int?i?=?0;?i?<?8;?i++)?{new?Thread(new?Runnable()?{@Overridepublic?void?run()?{while?(true)?{Future<String>?future?=?threadPoolTest.submit();try?{String?s?=?future.get();}?catch?(InterruptedException?e)?{e.printStackTrace();}?catch?(ExecutionException?e)?{e.printStackTrace();}?catch?(Error?e)?{e.printStackTrace();}}}}).start();}????????//子線程不停gc,模擬偶發的gcnew?Thread(new?Runnable()?{@Overridepublic?void?run()?{while?(true)?{System.gc();}}}).start();}????/***?異步執行任務*?@return*/public?Future<String>?submit()?{//關鍵點,通過Executors.newSingleThreadExecutor創建一個單線程的線程池ExecutorService?executorService?=?Executors.newSingleThreadExecutor();FutureTask<String>?futureTask?=?new?FutureTask(new?Callable()?{@Overridepublic?Object?call()?throws?Exception?{Thread.sleep(50);return?System.currentTimeMillis()?+?"";}});executorService.execute(futureTask);return?futureTask;}}

分析&疑問

第一個思考的問題是:線程池為什么關閉了,代碼中并沒有手動關閉的地方。看一下Executors.newSingleThreadExecotor的源碼實現:

public?static?ExecutorService?newSingleThreadExecutor()?{return?new?FinalizableDelegatedExecutorService(new?ThreadPoolExecutor(1,?1,?0L,?TimeUnit.MILLISECONDS,?new?LinkedBlockingQueue<Runnable>())); }

這里創建的實際上是一個FinalizableDelegatedExecutorService,這個包裝類重寫了finalize函數,也就是說這個類會在被GC回收之前,先執行線程池的shutdown方法。

問題來了,GC只會回收不可達(unreachable)的對象,在submit函數的棧幀未執行完出棧之前,executorService應該是可達的才對。

對于此問題,先拋出結論:

當對象仍存在于作用域(stack frame)時,finalize也可能會被執行

oracle jdk文檔中有一段關于finalize的介紹:

A reachable object is any object that can be accessed in any potential continuing computation from any live thread.

Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable. For example, a Java compiler or code generator may choose to set a variable or parameter that will no longer be used to null to cause the storage for such an object to be potentially reclaimable sooner.

大概意思是:可達對象(reachable object)是可以從任何活動線程的任何潛在的持續訪問中的任何對象;java編譯器或代碼生成器可能會對不再訪問的對象提前置為null,使得對象可以被提前回收。

也就是說,在jvm的優化下,可能會出現對象不可達之后被提前置空并回收的情況

舉個例子來驗證一下(摘自:https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope):

class?A?{@Override?protected?void?finalize()?{System.out.println(this?+?"?was?finalized!");}????public?static?void?main(String[]?args)?throws?InterruptedException?{A?a?=?new?A();System.out.println("Created?"?+?a);for?(int?i?=?0;?i?<?1\_000\_000\_000;?i++)?{if?(i?%?1\_000_00?==?0)System.gc();}System.out.println("done.");} }//打印結果Created?A@1be6f5c3 A@1be6f5c3?was?finalized! //finalize方法輸出 done.

從例子中可以看到,如果a在循環完成后已經不再使用了,則會出現先執行finalize的情況;雖然從對象作用域來說,方法沒有執行完,棧幀并沒有出棧,但是還是會被提前執行。

現在來增加一行代碼,在最后一行打印對象a,讓編譯器/代碼生成器認為后面有對象a的引用,

...System.out.println(a);//打印結果 Created?A@1be6f5c3 done. A@1be6f5c3

從結果上看,finalize方法都沒有執行(因為main方法執行完成后進程直接結束了),更不會出現提前finalize的問題了。

基于上面的測試結果,再測試一種情況,在循環之前先將對象a置為null,并且在最后打印保持對象a的引用

A?a?=?new?A(); System.out.println("Created?"?+?a); a?=?null; //手動置null for?(int?i?=?0;?i?<?1\_000\_000\_000;?i++)?{if?(i?%?1\_000_00?==?0)System.gc(); } System.out.println("done."); System.out.println(a); //打印結果Created? A@1be6f5c3 A@1be6f5c3?was?finalized! done.null

從結果上看,手動置null的話也會導致對象被提前回收,雖然在最后還有引用,但此時引用的也是null了


現在再回到上面的線程池問題,根據上面介紹的機制,在分析沒有引用之后,對象會被提前finalize

可在上述代碼中,return之前明明是有引用的executorService.execute(futureTask),為什么也會提前finalize呢?

猜測可能是由于在execute方法中,會調用threadPoolExecutor,會創建并啟動一個新線程,這時會發生一次主動的線程切換,導致在活動線程中對象不可達。

結合上面Oracle Jdk文檔中的描述“可達對象(reachable object)是可以從任何活動線程的任何潛在的持續訪問中的任何對象”,可以認為可能是因為一次顯示的線程切換,對象被認為不可達了,導致線程池被提前finalize了

下面來驗證一下猜想:

//入口函數 public?class?FinalizedTest?{????public?static?void?main(String []?args)?{final?FinalizedTest?finalizedTest?=?new?FinalizedTest();for?(int?i?=?0;?i?<?8;?i++)?{new?Thread(new?Runnable()?{@Overridepublic?void?run()?{while?(true)?{TFutureTask?future?=?finalizedTest.submit();}}}).start();}new?Thread(new?Runnable()?{@Overridepublic?void?run()?{while?(true)?{System.gc();}}}).start();}public?TFutureTask?submit(){TExecutorService?TExecutorService?=?Executors.create();TExecutorService.execute();return?null;} }//Executors.java,模擬juc的Executors public?class?Executors?{/***?模擬Executors.createSingleExecutor*?@return*/public?static?TExecutorService?create(){return?new?FinalizableDelegatedTExecutorService(new?TThreadPoolExecutor());}static?class?FinalizableDelegatedTExecutorService?extends?DelegatedTExecutorService?{FinalizableDelegatedTExecutorService(TExecutorService?executor)?{super(executor);}/***?析構函數中執行shutdown,修改線程池狀態*?@throws?Throwable*/@Overrideprotected?void?finalize()?throws?Throwable?{super.shutdown();}}static?class?DelegatedTExecutorService?extends?TExecutorService?{protected?TExecutorService?e;public?DelegatedTExecutorService(TExecutorService?executor)?{this.e?=?executor;}@Overridepublic?void?execute()?{e.execute();}@Overridepublic?void?shutdown()?{e.shutdown();}} }//TThreadPoolExecutor.java,模擬juc的ThreadPoolExecutorpublic?class?TThreadPoolExecutor?extends?TExecutorService?{/***?線程池狀態,false:未關閉,true已關閉*/private?AtomicBoolean?ctl?=?new?AtomicBoolean();@Overridepublic?void?execute()?{//啟動一個新線程,模擬ThreadPoolExecutor.executenew?Thread(new?Runnable()?{@Overridepublic?void?run()?{}}).start();//模擬ThreadPoolExecutor,啟動新建線程后,循環檢查線程池狀態,驗證是否會在finalize中shutdown//如果線程池被提前shutdown,則拋出異常for?(int?i?=?0;?i?<?1\_000\_000;?i++)?{if(ctl.get()){throw?new?RuntimeException("reject!!!\["+ctl.get()+"\]");}}}@Overridepublic?void?shutdown()?{ctl.compareAndSet(false,true);} }

執行若干時間后報錯:

Exception?in?thread?"Thread-1"?java.lang.RuntimeException:?reject!!!\[true\]

從錯誤上來看,“線程池”同樣被提前shutdown了,那么一定是由于新建線程導致的嗎?

下面將新建線程修改為Thread.sleep測試一下:

//TThreadPoolExecutor.java,修改后的execute方法 public?void?execute()?{try?{//顯式的sleep?1?ns,主動切換線程TimeUnit.NANOSECONDS.sleep(1);}?catch?(InterruptedException?e)?{e.printStackTrace();}//模擬ThreadPoolExecutor,啟動新建線程后,循環檢查線程池狀態,驗證是否會在finalize中shutdown//如果線程池被提前shutdown,則拋出異常for?(int?i?=?0;?i?<?1\_000\_000;?i++)?{if(ctl.get()){throw?new?RuntimeException("reject!!!\["+ctl.get()+"\]");}} }

執行結果一樣是報錯

Exception?in?thread?"Thread-3"?java.lang.RuntimeException:?reject!!!\[true\]

由此可得,如果在執行的過程中,發生一次顯式的線程切換,則會讓編譯器/代碼生成器認為外層包裝對象不可達

總結

雖然GC只會回收不可達GC ROOT的對象,但是在編譯器(沒有明確指出,也可能是JIT)/代碼生成器的優化下,可能會出現對象提前置null,或者線程切換導致的“提前對象不可達”的情況。

所以如果想在finalize方法里做些事情的話,一定在最后顯示的引用一下對象(toString/hashcode都可以),保持對象的可達性(reachable)

上面關于線程切換導致的對象不可達,沒有官方文獻的支持,只是個人一個測試結果,如有問題歡迎指出

綜上所述,這種回收機制并不是JDK的bug,而算是一個優化策略,提前回收而已;但Executors.newSingleThreadExecutor的實現里通過finalize來自動關閉線程池的做法是有Bug的,在經過優化后可能會導致線程池的提前shutdown,從而導致異常。

線程池的這個問題,在JDK的論壇里也是一個公開但未解決狀態的問題https://bugs.openjdk.java.net/browse/JDK-8145304。

不過在JDK11下,該問題已經被修復:

JUC??Executors.FinalizableDelegatedExecutorServicepublic?void?execute(Runnable?command)?{????try?{e.execute(command);}?finally?{?reachabilityFence(this);?} }

作者:空無

https://segmentfault.com/a/1190000021109130

《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

總結

以上是生活随笔為你收集整理的一个线程池 bug 引发的 GC 思考!的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 国产乱色精品成人免费视频 | 日产精品久久久久久久蜜臀 | 久久久久久九九九九九 | 欧洲av无码放荡人妇网站 | 折磨小男生性器羞耻的故事 | 三级艳丽杨钰莹三级 | 亚洲88av | 臭脚猛1s民工调教奴粗口视频 | 日韩电影在线一区二区 | 3p在线播放 | 一区二区三区视频免费观看 | 日韩天堂在线观看 | 麻豆av电影在线观看 | 欧美1| 婷婷视频网 | 国产精品1区2区3区 在线看黄的网站 | 精品久久久久久亚洲 | 伊人免费视频 | 色啪视频 | 鲁鲁狠狠狠7777一区二区 | 日本簧片在线观看 | 野外做受又硬又粗又大视频√ | 精品国产伦一区二区三区免费 | 91成人短视频 | 佐山爱av在线 | 国产乱码一区二区三区 | 久久9精品区-无套内射无码 | 九七影院在线观看免费观看电视 | 在线观看av的网站 | 成人激情视频网 | 欧美亚洲国产一区二区三区 | 国产无码精品在线播放 | 俺也去网站 | 欧美一二三视频 | 日韩一二三四 | 精品少妇久久久 | 久久久久久久久久艹 | 天天舔天天插 | 中文字幕超清在线观看 | 亚洲最大在线视频 | 91视频影院| 人妻互换一二三区激情视频 | 国产又黄又爽视频 | 一区二区视频在线免费观看 | 伊人ab| 日韩中文字幕免费在线观看 | 成人在线一区二区三区 | 久久久久婷婷 | 2019天天干天天操 | av调教| 国产夫绿帽单男3p精品视频 | 日韩一二三四五区 | 亚洲美女黄色片 | 久久99热人妻偷产国产 | 成人羞羞网站 | 狠狠干美女| 久久久久成人网 | 欧美成人不卡视频 | 久久国产三级 | 黄色羞羞网站 | 制服丝袜第一页在线观看 | 女的被男的操 | sm捆绑调教视频 | 91啦丨九色丨刺激 | 青青草社区视频 | 美女又爽又黄视频毛茸茸 | 国产精品99一区二区三区 | 成人三级在线视频 | 好吊色一区二区三区 | 美国黄色av | 国产精品对白 | 亚洲国产精华液网站w | 成人免费观看视频 | 日韩精品久久久 | 二区视频在线观看 | 欧美日韩国产综合在线 | 麻豆视频成人 | 91亚洲国产成人久久精品网站 | 黄色成人在线网站 | 91麻豆精品国产 | 五月激情小说网 | 国产一区91 | 午夜色综合 | 无码国内精品人妻少妇蜜桃视频 | 亚洲色图综合 | 午夜av免费观看 | 六月婷婷中文字幕 | 亚洲综合av网 | 91久久精品一区 | 色婷婷社区 | 手机免费av | 欧美成人精品二区三区99精品 | 欧美1234区 | 亚洲网址| 天天干天天干天天操 | 中国美女性猛交 | 国产激情视频在线观看 | 午夜激情久久 | 国产一二三区在线视频 |