Log4j2打印異常日志(AsyncAppender)導(dǎo)致線程Block

前言

? ? ? 隨著業(yè)務(wù)的快速增長慌洪,日志導(dǎo)致的線程 Block 問題愈發(fā)頻繁只冻。比如調(diào)用后端 RPC 服務(wù)超時株憾,導(dǎo)致調(diào)用方大量線程 Block蝙寨;再比如,業(yè)務(wù)內(nèi)部輸出異常日志導(dǎo)致服務(wù)大量線程 Block 等号胚,這些問題嚴(yán)重影響著服務(wù)的穩(wěn)定性籽慢。

AsyncAppender 導(dǎo)致線程 Block

? ? ? ? 通過監(jiān)控平臺查看線程監(jiān)控指標(biāo)浸遗,從 Blocked 線程堆棧不難看出是跟日志打印相關(guān)猫胁,由于是 ERROR 級別日志,查看具體報錯日志跛锌,發(fā)現(xiàn)有兩種業(yè)務(wù)異常弃秆。

? ? ? ? 從 Blocked 線程堆棧中可以看出,線程阻塞在類加載流程上髓帽,發(fā)現(xiàn)加載類時確實會根據(jù)類名來加 synchronized 同步塊菠赚,因此初步猜測是類加載導(dǎo)致線程 Block。

注意:

1郑藏、項目代碼里只是普通地輸出一條 ERROR 日志而已衡查,為何會觸發(fā)類加載?

2必盖、通常情況下類加載幾乎不會觸發(fā)線程 Block拌牲,不然一個項目要加載成千上萬個類,如果因為加載類就導(dǎo)致 Block歌粥,那項目就沒法正常運行了塌忽。

為什么會觸發(fā)類加載?

? ? ? 從 Blocked 線程堆棧著手分析失驶,查看堆棧中的 ThrowableProxy 相關(guān)代碼土居,發(fā)現(xiàn)其構(gòu)造函數(shù)會遍歷整個異常堆棧中的所有堆棧元素,最終獲取所有堆棧元素類所在的 JAR 名稱和版本信息

具體流程如下:

1擦耀、首先獲取堆棧元素的類名稱棉圈。

2、再通過 loadClass 的方式獲取對應(yīng)的 Class 對象眷蜓。

3迄损、進(jìn)一步獲取該類所在的 JAR 信息,從 CodeSource 中獲取 JAR 名稱账磺,從Package 中獲取 JAR 版本芹敌。

? ? ? ? 當(dāng)前問題已在2.11.1版本中修復(fù),通過讓 get/put 方法使用同一個 key 來修復(fù)緩存的有效性問題垮抗,但由于 ThrowableProxy 對每個 Throwable 都會創(chuàng)建一個全新的 Map氏捞,而不是使用全局 Map,因此其緩存也僅僅對單個 Throwable 生效冒版。

? ? ? ? 上述問題從源碼中可以看到液茎,ThrowableProxy#toExtendedStackTrace 方法通過 Map緩存當(dāng)前堆棧元素類對應(yīng)的 CacheEntry,來避免重復(fù)解析 CacheEntry辞嗡,但是由于Map 緩存 put 操作使用的 key 來自于 StackTraceElement.toString 方法捆等,而 get操作使用的 key 卻來自于 StackTraceElement.getClassName 方法,即使對于同一個 StackTraceElement 而言续室,其 toString 和 getClassName 方法對應(yīng)的返回結(jié)果也不一樣栋烤,所以此 map 形同虛設(shè)。

? ? ? 通常情況下一個類加載器對于一個類只會加載一次挺狰,類加載器內(nèi)部保存有類緩存明郭,無需重復(fù)加載,但目前的現(xiàn)象卻是由于類加載而導(dǎo)致線程大量 Block丰泊,因此必然是有些類加載不了薯定,且不斷重復(fù)嘗試加載,那到底是什么類無法加載呢

到底什么類加載不了瞳购?(分析業(yè)務(wù)異常的具體堆棧)

兩份堆椈爸叮基本相似,且大多數(shù)類都是很普通的類学赛,但是唯一不同的地方在于:

1年堆、sun.reflect.NativeMethodAccessorImpl

2、sun.reflect.GeneratedMethodAccessor261

? ? ? 從字面信息中不難猜測出這與反射調(diào)用相關(guān)罢屈,但問題是這兩份堆棧對應(yīng)的其實是同一份業(yè)務(wù)代碼嘀韧,查閱相關(guān)資料得知,這與 JVM 反射調(diào)用相關(guān).

JVM 對反射調(diào)用分兩種情況:

1缠捌、默認(rèn)使用 native 方法進(jìn)行反射操作锄贷。

2译蒂、一定條件下會生成字節(jié)碼進(jìn)行反射操作,即生成 sun.reflect.GeneratedMethodAccessor<N> 類谊却,它是一個反射調(diào)用方法的包裝類柔昼,代理不同的方法,類后綴序號遞增炎辨。

? ? ? 從源碼的 MethodAccessorGenerator#generateName 方法可以看到捕透,字節(jié)碼生成的類名稱規(guī)則是 sun.reflect.GeneratedConstructorAccessor<N>,其中 N 是從 0 開始的遞增數(shù)字碴萧,且生成類是由 DelegatingClassLoader 類加載器定義乙嘀,所以其他類加載器無法加載該類,也就無法生成類緩存數(shù)據(jù)破喻,從而導(dǎo)致每次加載類時都需要遍歷JarFile虎谢,極大地降低了類查找速度,且類加載過程是 synchronized 同步調(diào)用曹质,在高并發(fā)情況下會更加惡化婴噩,從而導(dǎo)致線程 Block。

JVM 反射調(diào)用為什么要做這么做羽德?

? ? ? 這是 JVM 對反射調(diào)用的一種優(yōu)化手段几莽,在 sun.reflect.ReflectionFactory 的文檔注釋里對此做了解釋,這是一種“Inflation”機(jī)制宅静,加載字節(jié)碼的調(diào)用方式在第一次調(diào)用時會比 Native 調(diào)用的速度要慢 3~4 倍章蚣,但是在后續(xù)調(diào)用時會比 Native 調(diào)用速度快 20 多倍。為了避免反射調(diào)用影響應(yīng)用的啟動速度坏为,所以在反射調(diào)用的前幾次通過 Native 方式調(diào)用究驴,當(dāng)超過一定調(diào)用次數(shù)后使用字節(jié)碼方式調(diào)用,提升反射調(diào)用性能匀伏。

普通通地打印一條 ERROR 日志,為何會導(dǎo)致解析蝴韭、加載異常堆棧類够颠?

? ? ? ? ? AsyncAppender 在生成 LogEvent 的快照 Log4jLogEvent 時,會先對 LogEvent序列化處理統(tǒng)一轉(zhuǎn)換為 LogEventProxy榄鉴。

此時不同類型的 LogEvent 的處理情況稍有差異:

● Log4jLogEvent 類 型履磨, 先 執(zhí) 行 Log4jLogEvent#getThrownProxy 方 法,觸發(fā)創(chuàng)建 ThrowableProxy 實例庆尘。

● MutableLogEvent 類型剃诅,先創(chuàng)建 LogEventProxy 實例,在構(gòu)造函數(shù)內(nèi)執(zhí)行 MutableLogEvent#getThrownProxy 方法驶忌,觸發(fā)創(chuàng)建 ThrowableProxy實例矛辕。

? ? ? ? 不管 LogEvent 的實際類型是 MutableLogEvent 還是 Log4jLogEvent笑跛,最終都會觸發(fā)創(chuàng)建 ThrowableProxy 實例,并在 ThrowableProxy 構(gòu)造函數(shù)內(nèi)觸發(fā)了解析聊品、加載異常堆棧類飞蹂。

問題小結(jié)

? ? ? ? Log4j2 打印異常日志時,AsyncAppender 會先創(chuàng)建日志事件快照翻屈,并進(jìn)一步觸發(fā)解析陈哑、加載異常堆棧類JVM 通過生成字節(jié)碼的方式優(yōu)化反射調(diào)用性能伸眶,但該動態(tài)生成的類無法被 WebAppClassLoader 類加載器加載惊窖,因此當(dāng)大量包含反射調(diào)用的異常堆棧被輸出到日志時,會頻繁地觸發(fā)類加載厘贼,由于類加載過程是 synchronized同步加鎖的爬坑,且每次加載都需要讀取文件,速度較慢涂臣,從而導(dǎo)致線程 Block盾计。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市赁遗,隨后出現(xiàn)的幾起案子署辉,更是在濱河造成了極大的恐慌,老刑警劉巖岩四,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哭尝,死亡現(xiàn)場離奇詭異,居然都是意外死亡剖煌,警方通過查閱死者的電腦和手機(jī)材鹦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耕姊,“玉大人桶唐,你說我怎么就攤上這事≤岳迹” “怎么了尤泽?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長规脸。 經(jīng)常有香客問我坯约,道長,這世上最難降的妖魔是什么莫鸭? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任闹丐,我火速辦了婚禮,結(jié)果婚禮上被因,老公的妹妹穿的比我還像新娘卿拴。我一直安慰自己魂迄,他們只是感情好剑勾,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布互捌。 她就那樣靜靜地躺著黍少,像睡著了一般。 火紅的嫁衣襯著肌膚如雪航徙。 梳的紋絲不亂的頭發(fā)上如贷,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音到踏,去河邊找鬼杠袱。 笑死,一個胖子當(dāng)著我的面吹牛窝稿,可吹牛的內(nèi)容都是我干的楣富。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼伴榔,長吁一口氣:“原來是場噩夢啊……” “哼纹蝴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起踪少,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤塘安,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后援奢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兼犯,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年集漾,在試婚紗的時候發(fā)現(xiàn)自己被綠了切黔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡具篇,死狀恐怖纬霞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情栽连,我是刑警寧澤险领,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站秒紧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏挨下。R本人自食惡果不足惜熔恢,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望臭笆。 院中可真熱鬧叙淌,春花似錦秤掌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至茂洒,卻和暖如春孟岛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背督勺。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工渠羞, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人智哀。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓次询,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瓷叫。 傳聞我的和親對象是個殘疾皇子屯吊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容